From ec29ba20dfd4abc0cb74366b39dda06af136e073 Mon Sep 17 00:00:00 2001 From: Apple Date: Fri, 4 Mar 2016 22:18:29 +0000 Subject: [PATCH] ld64-264.3.101.tar.gz --- doc/man/man1/ld.1 | 17 +- ld64.xcodeproj/project.pbxproj | 117 ++- src/abstraction/MachOTrie.hpp | 4 +- src/ld/InputFiles.cpp | 44 +- src/ld/InputFiles.h | 9 +- src/ld/LinkEditClassic.hpp | 19 +- src/ld/Options.cpp | 535 +++++++---- src/ld/Options.h | 28 +- src/ld/OutputFile.cpp | 42 +- src/ld/OutputFile.h | 2 +- src/ld/Resolver.cpp | 12 +- src/ld/SymbolTable.cpp | 12 + src/ld/SymbolTable.h | 1 + src/ld/ld.cpp | 17 +- src/ld/ld.hpp | 16 +- src/ld/parsers/generic_dylib_file.hpp | 565 +++++++++++ src/ld/parsers/lto_file.cpp | 10 +- src/ld/parsers/lto_file.h | 3 +- src/ld/parsers/macho_dylib_file.cpp | 882 +++++------------- src/ld/parsers/macho_dylib_file.h | 6 +- src/ld/parsers/macho_relocatable_file.cpp | 289 +++--- src/ld/parsers/macho_relocatable_file.h | 1 + src/ld/parsers/textstub_dylib_file.cpp | 664 ++++--------- src/ld/passes/bitcode_bundle.cpp | 34 +- src/ld/passes/branch_island.cpp | 14 +- src/ld/passes/branch_shim.cpp | 2 +- src/ld/passes/code_dedup.cpp | 375 ++++++++ src/ld/passes/code_dedup.h | 45 + src/ld/passes/compact_unwind.cpp | 38 +- src/ld/passes/objc.cpp | 17 +- src/ld/passes/stubs/stubs.cpp | 10 +- src/other/machochecker.cpp | 435 +++++---- src/other/unwinddump.cpp | 2 +- unit-tests/run-all-unit-tests | 2 - .../AdrpLdrGotLdrField.s | 53 ++ .../linker-optimization-hints/Makefile | 366 +++++++- .../linker_options-library-chain/Makefile | 47 + .../linker_options-library-chain/bar.c | 1 + .../linker_options-library-chain/foo.c | 8 + .../linker_options-library-chain/main.c | 8 + .../linker_options-library-chain/subbar.c | 1 + .../objc-category-optimize-load/Makefile | 2 +- .../objc-category-optimize/Makefile | 8 +- 43 files changed, 2958 insertions(+), 1805 deletions(-) create mode 100644 src/ld/parsers/generic_dylib_file.hpp create mode 100644 src/ld/passes/code_dedup.cpp create mode 100644 src/ld/passes/code_dedup.h create mode 100644 unit-tests/test-cases/linker-optimization-hints/AdrpLdrGotLdrField.s create mode 100644 unit-tests/test-cases/linker_options-library-chain/Makefile create mode 100644 unit-tests/test-cases/linker_options-library-chain/bar.c create mode 100644 unit-tests/test-cases/linker_options-library-chain/foo.c create mode 100644 unit-tests/test-cases/linker_options-library-chain/main.c create mode 100644 unit-tests/test-cases/linker_options-library-chain/subbar.c diff --git a/doc/man/man1/ld.1 b/doc/man/man1/ld.1 index 1c118b8..53fbaa5 100644 --- a/doc/man/man1/ld.1 +++ b/doc/man/man1/ld.1 @@ -362,7 +362,7 @@ The argument .Ar size is a hexadecimal number with an optional leading 0x. The .Ar size -should be an even multiple of 4KB, that is the last three hexadecimal digits should be zero. +should be a multiple of the architecture's page size (4KB or 16KB). .It Fl allow_stack_execute Marks executable so that all stacks in the task will be given stack execution privilege. This includes pthread stacks. .It Fl export_dynamic @@ -541,10 +541,21 @@ Otherwise, the reverse map will be written to a file at .Bl -tag .It Fl v Prints the version of the linker. +.It Fl no_deduplicate +Don't run deduplication pass in linker +.It Fl verbose_deduplicate +Prints names of functions that are eliminated by deduplication and total code savings size. .It Fl dirty_data_list Ar filename Specifies a file containing the names of data symbols likely to be dirtied. If the linker is creating a __DATA_DIRTY segment, those symbols will be moved to that segment. +.It Fl max_default_common_align Ar value +Any common symbols (aka tentative definitions, or uninitialized (zeroed) variables) that have no explicit alignment +are normally aligned to their next power of two size (e.g. a 240 byte array is 256 aligned). +This option lets you reduce the max alignment. For instance, a value of 0x40 would reduce +the alignment for a 240 byte array to 64 bytes (instead of 256). The value specified must be a hexadecimal power of two +If -max_default_common_align is not used, the default alignment is already +limited to 0x8 (2^3) bytes for -preload and 0x8000 (2^15) for all other output types. .It Fl move_to_rw_segment Ar segment_name Ar filename Moves data symbols to another segment. The command line option specifies the target segment name and a path to a file containing a list of symbols to move. @@ -760,6 +771,10 @@ option is used, the temporary file will be stored at the specified path and rema is complete. Without the option, the linker picks a path and deletes the object file before the linker tool completes, thus tools such as the debugger or dsymutil will not be able to access the DWARF debug info in the temporary object file. +.It Fl lto_library Ar path +When performing Link Time Optimization (LTO), the linker normally loads libLTO.dylib relative to the linker +binary (../lib/libLTO.dylib). This option allows the user to specify the path to a specific libLTO.dylib +to load instead. .It Fl page_align_data_atoms During development, this option can be used to space out all global variables so each is on a separate page. This is useful when analyzing dirty and resident pages. The information can then be used to create an diff --git a/ld64.xcodeproj/project.pbxproj b/ld64.xcodeproj/project.pbxproj index 1c5e622..c47b806 100644 --- a/ld64.xcodeproj/project.pbxproj +++ b/ld64.xcodeproj/project.pbxproj @@ -28,7 +28,6 @@ isa = PBXAggregateTarget; buildConfigurationList = F9B1A26C0A3A568700DA8FAB /* Build configuration list for PBXAggregateTarget "all" */; buildPhases = ( - F9871A3413340B4600DB3F24 /* Platform install */, ); dependencies = ( F9B1A2690A3A568200DA8FAB /* PBXTargetDependency */, @@ -36,6 +35,7 @@ F9C12EEA0ED65765005BC69D /* PBXTargetDependency */, F9B8135D0EC2620E00F94C13 /* PBXTargetDependency */, F9A3DE160ED76D9A00C590B9 /* PBXTargetDependency */, + F9FF3BDD1C586D7C0015D843 /* PBXTargetDependency */, ); name = all; productName = all; @@ -83,6 +83,7 @@ F9EA7584097882F3008B4F1D /* debugline.c in Sources */ = {isa = PBXBuildFile; fileRef = F9EA7582097882F3008B4F1D /* debugline.c */; }; F9EA75BC09788857008B4F1D /* debugline.c in Sources */ = {isa = PBXBuildFile; fileRef = F9EA7582097882F3008B4F1D /* debugline.c */; }; F9EC78060A2F8674002A3E39 /* rebase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9EC78050A2F8674002A3E39 /* rebase.cpp */; }; + F9FC510A1BC893C400FEC3F8 /* code_dedup.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9FC51081BC8915A00FEC3F8 /* code_dedup.cpp */; }; FA95D6141AB25CF400395811 /* textstub_dylib_file.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA95D6121AB25CF400395811 /* textstub_dylib_file.cpp */; }; /* End PBXBuildFile section */ @@ -183,13 +184,20 @@ remoteGlobalIDString = F9BA51600ECE58BE00D1D62E; remoteInfo = dyldinfo; }; + F9FF3BDC1C586D7C0015D843 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F9023C3006D5A227001BBF46 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F9EA72CA097454A6008B4F1D; + remoteInfo = machocheck; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ F97F5025070D0B6300B9FCD7 /* copy man page */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 8; - dstPath = /usr/share/man/man1; + dstPath = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/share/man/man1"; dstSubfolderSpec = 0; files = ( F97F5029070D0BB200B9FCD7 /* ld.1 in copy man page */, @@ -200,7 +208,7 @@ F9A3DE140ED76D7700C590B9 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 8; - dstPath = "/usr/local/include/mach-o"; + dstPath = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/local/include/mach-o"; dstSubfolderSpec = 0; files = ( F9A3DE1E0ED7738300C590B9 /* prune_trie.h in CopyFiles */, @@ -210,7 +218,7 @@ F9B1A25E0A3A44CB00DA8FAB /* install man page */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 8; - dstPath = /usr/share/man/man1; + dstPath = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/share/man/man1"; dstSubfolderSpec = 0; files = ( F9B1A2640A3A563E00DA8FAB /* rebase.1 in install man page */, @@ -221,7 +229,7 @@ F9B813870EC2659600F94C13 /* install man page */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 8; - dstPath = usr/share/man/man1; + dstPath = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/share/man/man1"; dstSubfolderSpec = 0; files = ( F9B813850EC2657800F94C13 /* unwinddump.1 in install man page */, @@ -232,7 +240,7 @@ F9C12EA50ED63E05005BC69D /* install man page */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 8; - dstPath = usr/share/man/man1; + dstPath = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/share/man/man1"; dstSubfolderSpec = 0; files = ( F9C12EA30ED63DE7005BC69D /* dyldinfo.1 in install man page */, @@ -338,6 +346,9 @@ F9EA7583097882F3008B4F1D /* debugline.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = debugline.h; path = src/ld/debugline.h; sourceTree = ""; tabWidth = 4; usesTabs = 1; }; F9EC77EE0A2F85F6002A3E39 /* rebase */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = rebase; sourceTree = BUILT_PRODUCTS_DIR; }; F9EC78050A2F8674002A3E39 /* rebase.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = rebase.cpp; path = src/other/rebase.cpp; sourceTree = ""; tabWidth = 4; usesTabs = 1; }; + F9FC51081BC8915A00FEC3F8 /* code_dedup.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = code_dedup.cpp; sourceTree = ""; }; + F9FC51091BC8915A00FEC3F8 /* code_dedup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = code_dedup.h; sourceTree = ""; }; + FA4843BE1B7279ED001C8025 /* generic_dylib_file.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = generic_dylib_file.hpp; sourceTree = ""; }; FA95D6121AB25CF400395811 /* textstub_dylib_file.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = textstub_dylib_file.cpp; sourceTree = ""; usesTabs = 1; }; FA95D6131AB25CF400395811 /* textstub_dylib_file.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = textstub_dylib_file.hpp; sourceTree = ""; }; /* End PBXFileReference section */ @@ -428,6 +439,8 @@ F9AA650B1051BD2B003E3539 /* passes */ = { isa = PBXGroup; children = ( + F9FC51081BC8915A00FEC3F8 /* code_dedup.cpp */, + F9FC51091BC8915A00FEC3F8 /* code_dedup.h */, B028FCF11A9E7C3F00E3584B /* bitcode_bundle.cpp */, B028FCF01A9E7B4A00E3584B /* bitcode_bundle.h */, F984A38010BB4B0D009E9878 /* branch_island.cpp */, @@ -475,8 +488,6 @@ F9AA65861051E750003E3539 /* parsers */ = { isa = PBXGroup; children = ( - FA95D6121AB25CF400395811 /* textstub_dylib_file.cpp */, - FA95D6131AB25CF400395811 /* textstub_dylib_file.hpp */, F91B7B0218987D5F0099486F /* libunwind */, F9AA6784105700C2003E3539 /* opaque_section_file.cpp */, F9AA6785105700C2003E3539 /* opaque_section_file.h */, @@ -484,8 +495,11 @@ F9AA65D81051EC4A003E3539 /* archive_file.h */, F9AA65D91051EC4A003E3539 /* lto_file.cpp */, F9AA65DA1051EC4A003E3539 /* lto_file.h */, + FA4843BE1B7279ED001C8025 /* generic_dylib_file.hpp */, F9AA65DB1051EC4A003E3539 /* macho_dylib_file.cpp */, F9AA65DC1051EC4A003E3539 /* macho_dylib_file.h */, + FA95D6121AB25CF400395811 /* textstub_dylib_file.cpp */, + FA95D6131AB25CF400395811 /* textstub_dylib_file.hpp */, F9AA65871051E750003E3539 /* macho_relocatable_file.cpp */, F9AA65881051E750003E3539 /* macho_relocatable_file.h */, ); @@ -772,21 +786,6 @@ shellScript = "# Let tests set MACOSX_DEPLOYMENT_TARGET as they need\nunsetenv MACOSX_DEPLOYMENT_TARGET\n\n# make linker relative libLTO.dylib\nmkdir -p ${BUILD_DIR}/lib\nln -sf /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libLTO.dylib ${BUILD_DIR}/lib/libLTO.dylib\n\n# always use new linker\nsetenv LD_NO_CLASSIC_LINKER\nsetenv LD_NO_CLASSIC_LINKER_STATIC\n\n# run full test suite\n\"$SRCROOT\"/unit-tests/run-all-unit-tests\n\nexit 0"; showEnvVarsInLog = 0; }; - F9871A3413340B4600DB3F24 /* Platform install */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 8; - files = ( - ); - inputPaths = ( - ); - name = "Platform install"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 1; - shellPath = /bin/sh; - shellScript = "\nif [ -n \"${DT_TOOLCHAIN_DIR}\" ]\nthen\n\tmkdir -p \"${DSTROOT}/${DT_VARIANT}/${DT_TOOLCHAIN_DIR}\"\n\tmv ${DSTROOT}/usr \"${DSTROOT}/${DT_VARIANT}/${DT_TOOLCHAIN_DIR}\"\nelse\n\tif [ -n \"${RC_PURPLE}\" ]\n\tthen\n\t\tmkdir -p ${DSTROOT}/Developer/Platforms/iPhoneOS.platform/Developer/\n\t\tmv ${DSTROOT}/usr ${DSTROOT}/Developer/Platforms/iPhoneOS.platform/Developer\n\telse\n\t\tmkdir -p ${DSTROOT}/Developer/usr/bin\n\t\tcp ${DSTROOT}/usr/bin/ld ${DSTROOT}/Developer/usr/bin\n\tfi\nfi\n\n"; - showEnvVarsInLog = 0; - }; F9CCF765144CE244007CB524 /* make configure.h */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -906,6 +905,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F9FC510A1BC893C400FEC3F8 /* code_dedup.cpp in Sources */, FA95D6141AB25CF400395811 /* textstub_dylib_file.cpp in Sources */, F9C0D4BD06DD28D2001C7193 /* Options.cpp in Sources */, F9023C4E06D5A272001BBF46 /* ld.cpp in Sources */, @@ -1045,6 +1045,11 @@ target = F9BA51600ECE58BE00D1D62E /* dyldinfo */; targetProxy = F9F9AD67116D58AF0028EFAB /* PBXContainerItemProxy */; }; + F9FF3BDD1C586D7C0015D843 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F9EA72CA097454A6008B4F1D /* machocheck */; + targetProxy = F9FF3BDC1C586D7C0015D843 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -1060,9 +1065,11 @@ DEBUG_INFORMATION_FORMAT = dwarf; GCC_DYNAMIC_NO_PIC = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_INLINES_ARE_PRIVATE_EXTERN = NO; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = DEBUG; + GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; GCC_WARN_ABOUT_MISSING_NEWLINE = YES; @@ -1092,7 +1099,7 @@ "$(DEVELOPER_DIR)/usr/local/include", "$(DEVELOPER_DIR)/usr/include", ); - INSTALL_PATH = /usr/bin; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/bin"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/../lib/"; LINKER_DISPLAYS_MANGLED_NAMES = NO; MACOSX_DEPLOYMENT_TARGET = ""; @@ -1110,6 +1117,7 @@ ); PREBINDING = NO; PRODUCT_NAME = ld; + SDKROOT = macosx.internal; SECTORDER_FLAGS = ""; VERSIONING_SYSTEM = "apple-generic"; WARNING_CFLAGS = "-Wall"; @@ -1128,6 +1136,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_DYNAMIC_NO_PIC = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_INLINES_ARE_PRIVATE_EXTERN = NO; GCC_OPTIMIZATION_LEVEL = s; GCC_PREPROCESSOR_DEFINITIONS = ( "$(GCC_PREPROCESSOR_DEFINITIONS_QUOTED_FOR_TARGET_1)", @@ -1135,6 +1144,7 @@ "$(GCC_PREPROCESSOR_DEFINITIONS_$(RC_RELEASE))", ); GCC_PREPROCESSOR_DEFINITIONS_QUOTED_FOR_TARGET_1 = "LD_VERS='\"ld64-$(RC_ProjectSourceVersion)\"'"; + GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_TREAT_WARNINGS_AS_ERRORS = NO; GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; GCC_WARN_ABOUT_MISSING_NEWLINE = YES; @@ -1164,7 +1174,7 @@ "$(DEVELOPER_DIR)/usr/local/include", "$(DEVELOPER_DIR)/usr/include", ); - INSTALL_PATH = /usr/bin; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/bin"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/../lib/"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", @@ -1179,6 +1189,7 @@ ); PREBINDING = NO; PRODUCT_NAME = ld; + SDKROOT = macosx.internal; SECTORDER_FLAGS = ""; STRIP_INSTALLED_PRODUCT = YES; STRIP_STYLE = debugging; @@ -1212,7 +1223,7 @@ "$(DEVELOPER_DIR)/usr/local/include", "$(DT_TOOLCHAIN_DIR)/usr/local/include", ); - INSTALL_PATH = "$(HOME)/bin"; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/local/bin"; ONLY_ACTIVE_ARCH = NO; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", @@ -1225,6 +1236,7 @@ OTHER_REZFLAGS = ""; PREBINDING = NO; PRODUCT_NAME = ObjectDump; + SDKROOT = macosx.internal; SECTORDER_FLAGS = ""; WARNING_CFLAGS = ( "-Wmost", @@ -1250,7 +1262,7 @@ "$(DEVELOPER_DIR)/usr/local/include", "$(DT_TOOLCHAIN_DIR)/usr/local/include", ); - INSTALL_PATH = "$(HOME)/bin"; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/local/bin"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", "$(OTHER_CFLAGS)", @@ -1262,6 +1274,7 @@ OTHER_REZFLAGS = ""; PREBINDING = NO; PRODUCT_NAME = ObjectDump; + SDKROOT = macosx.internal; WARNING_CFLAGS = ( "-Wmost", "-Wno-four-char-constants", @@ -1336,12 +1349,14 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_DYNAMIC_NO_PIC = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_INLINES_ARE_PRIVATE_EXTERN = NO; GCC_OPTIMIZATION_LEVEL = s; GCC_PREPROCESSOR_DEFINITIONS = ( "$(GCC_PREPROCESSOR_DEFINITIONS_QUOTED_FOR_TARGET_1)", "$(GCC_PREPROCESSOR_DEFINITIONS_$(RC_RELEASE))", ); GCC_PREPROCESSOR_DEFINITIONS_QUOTED_FOR_TARGET_1 = "LD_VERS='\"ld64-$(RC_ProjectSourceVersion)\"'"; + GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_TREAT_WARNINGS_AS_ERRORS = NO; GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; GCC_WARN_ABOUT_MISSING_NEWLINE = YES; @@ -1371,7 +1386,7 @@ "$(DEVELOPER_DIR)/usr/local/include", "$(DEVELOPER_DIR)/usr/include", ); - INSTALL_PATH = /usr/bin; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/bin"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/../lib/"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", @@ -1386,6 +1401,7 @@ ); PREBINDING = NO; PRODUCT_NAME = ld; + SDKROOT = macosx.internal; SECTORDER_FLAGS = ""; STRIP_INSTALLED_PRODUCT = YES; STRIP_STYLE = debugging; @@ -1405,10 +1421,11 @@ GCC_MODEL_TUNING = G5; GCC_PREPROCESSOR_DEFINITIONS = "$(GCC_PREPROCESSOR_DEFINITIONS_$(RC_RELEASE))"; HEADER_SEARCH_PATHS = ""; - INSTALL_PATH = /usr/bin; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/bin"; OTHER_LDFLAGS = "-Wl,-exported_symbol,__mh_execute_header"; PREBINDING = NO; PRODUCT_NAME = rebase; + SDKROOT = macosx.internal; STRIP_INSTALLED_PRODUCT = YES; STRIP_STYLE = debugging; VALID_ARCHS = "i386 ppc x86_64"; @@ -1426,7 +1443,7 @@ GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; HEADER_SEARCH_PATHS = ""; - INSTALL_PATH = /usr/bin; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/bin"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", "$(OTHER_CFLAGS)", @@ -1437,6 +1454,7 @@ ); PREBINDING = NO; PRODUCT_NAME = unwinddump; + SDKROOT = macosx.internal; STRIP_INSTALLED_PRODUCT = YES; STRIP_STYLE = debugging; }; @@ -1459,7 +1477,7 @@ "$(DEVELOPER_DIR)/usr/local/include", "$(DT_TOOLCHAIN_DIR)/usr/local/include", ); - INSTALL_PATH = "$(HOME)/bin"; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/local/bin"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", "$(OTHER_CFLAGS)", @@ -1471,6 +1489,7 @@ OTHER_REZFLAGS = ""; PREBINDING = NO; PRODUCT_NAME = ObjectDump; + SDKROOT = macosx.internal; WARNING_CFLAGS = ( "-Wmost", "-Wno-four-char-constants", @@ -1488,7 +1507,7 @@ GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_MODEL_TUNING = G5; HEADER_SEARCH_PATHS = ""; - INSTALL_PATH = "$(HOME)/bin"; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/local/bin"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", "$(OTHER_CFLAGS)", @@ -1496,6 +1515,7 @@ OTHER_LDFLAGS = "-stdlib=libc++"; PREBINDING = NO; PRODUCT_NAME = machocheck; + SDKROOT = macosx.internal; }; name = "Release-assert"; }; @@ -1508,7 +1528,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_MODEL_TUNING = G5; - INSTALL_PATH = /usr/bin; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/bin"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", "$(OTHER_CFLAGS)", @@ -1516,6 +1536,7 @@ OTHER_LDFLAGS = "-stdlib=libc++"; PREBINDING = NO; PRODUCT_NAME = dyldinfo; + SDKROOT = macosx.internal; STRIP_INSTALLED_PRODUCT = YES; STRIP_STYLE = debugging; ZERO_LINK = NO; @@ -1532,7 +1553,7 @@ GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_MODEL_TUNING = G5; GCC_SYMBOLS_PRIVATE_EXTERN = YES; - INSTALL_PATH = /usr/local/lib; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/local/lib"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", "$(OTHER_CFLAGS)", @@ -1562,7 +1583,7 @@ GCC_OPTIMIZATION_LEVEL = 0; GCC_SYMBOLS_PRIVATE_EXTERN = YES; GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - INSTALL_PATH = /usr/local/lib; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/local/lib"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", "$(OTHER_CFLAGS)", @@ -1582,7 +1603,7 @@ GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_MODEL_TUNING = G5; GCC_SYMBOLS_PRIVATE_EXTERN = YES; - INSTALL_PATH = /usr/local/lib; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/local/lib"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", "$(OTHER_CFLAGS)", @@ -1625,7 +1646,7 @@ GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; - INSTALL_PATH = "$(HOME)/bin"; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/bin"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", "$(OTHER_CFLAGS)", @@ -1633,6 +1654,7 @@ OTHER_LDFLAGS = "-stdlib=libc++"; PREBINDING = NO; PRODUCT_NAME = unwinddump; + SDKROOT = macosx.internal; }; name = Debug; }; @@ -1647,7 +1669,7 @@ GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; HEADER_SEARCH_PATHS = ""; - INSTALL_PATH = /usr/bin; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/bin"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", "$(OTHER_CFLAGS)", @@ -1655,6 +1677,7 @@ OTHER_LDFLAGS = "-Wl,-exported_symbol,__mh_execute_header"; PREBINDING = NO; PRODUCT_NAME = unwinddump; + SDKROOT = macosx.internal; STRIP_INSTALLED_PRODUCT = YES; STRIP_STYLE = debugging; }; @@ -1680,7 +1703,7 @@ GCC_WARN_UNUSED_LABEL = NO; GCC_WARN_UNUSED_VALUE = YES; GCC_WARN_UNUSED_VARIABLE = YES; - INSTALL_PATH = /usr/local/bin; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/bin"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", "$(OTHER_CFLAGS)", @@ -1688,6 +1711,7 @@ OTHER_LDFLAGS = "-stdlib=libc++"; PREBINDING = NO; PRODUCT_NAME = dyldinfo; + SDKROOT = macosx.internal; WARNING_CFLAGS = "-Wall"; }; name = Debug; @@ -1701,7 +1725,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_MODEL_TUNING = G5; - INSTALL_PATH = /usr/bin; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/bin"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", "$(OTHER_CFLAGS)", @@ -1709,6 +1733,7 @@ OTHER_LDFLAGS = "-stdlib=libc++"; PREBINDING = NO; PRODUCT_NAME = dyldinfo; + SDKROOT = macosx.internal; STRIP_INSTALLED_PRODUCT = YES; STRIP_STYLE = debugging; ZERO_LINK = NO; @@ -1724,7 +1749,7 @@ GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; - INSTALL_PATH = "$(HOME)/bin"; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/local/bin"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", "$(OTHER_CFLAGS)", @@ -1732,6 +1757,7 @@ OTHER_LDFLAGS = "-stdlib=libc++"; PREBINDING = NO; PRODUCT_NAME = machocheck; + SDKROOT = macosx.internal; }; name = Debug; }; @@ -1744,7 +1770,7 @@ GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_MODEL_TUNING = G5; HEADER_SEARCH_PATHS = ""; - INSTALL_PATH = "$(HOME)/bin"; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/local/bin"; OTHER_CPLUSPLUSFLAGS = ( "-stdlib=libc++", "$(OTHER_CFLAGS)", @@ -1752,6 +1778,7 @@ OTHER_LDFLAGS = "-stdlib=libc++"; PREBINDING = NO; PRODUCT_NAME = machocheck; + SDKROOT = macosx.internal; }; name = Release; }; @@ -1765,9 +1792,10 @@ GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; - INSTALL_PATH = "$(HOME)/bin"; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/bin"; PREBINDING = NO; PRODUCT_NAME = rebase; + SDKROOT = macosx.internal; }; name = Debug; }; @@ -1781,10 +1809,11 @@ GCC_MODEL_TUNING = G5; GCC_PREPROCESSOR_DEFINITIONS = "$(GCC_PREPROCESSOR_DEFINITIONS_$(RC_RELEASE))"; HEADER_SEARCH_PATHS = ""; - INSTALL_PATH = /usr/bin; + INSTALL_PATH = "$(DT_VARIANT)/$(DT_TOOLCHAIN_DIR)/usr/bin"; OTHER_LDFLAGS = "-Wl,-exported_symbol,__mh_execute_header"; PREBINDING = NO; PRODUCT_NAME = rebase; + SDKROOT = macosx.internal; STRIP_INSTALLED_PRODUCT = YES; STRIP_STYLE = debugging; VALID_ARCHS = "i386 ppc x86_64"; diff --git a/src/abstraction/MachOTrie.hpp b/src/abstraction/MachOTrie.hpp index b7c55c1..575a654 100644 --- a/src/abstraction/MachOTrie.hpp +++ b/src/abstraction/MachOTrie.hpp @@ -351,6 +351,8 @@ static inline void processExportNode(const uint8_t* const start, const uint8_t* } output.push_back(e); } + if ( children > end ) + throw "malformed trie, terminalSize extends beyond trie data"; const uint8_t childrenCount = *children++; const uint8_t* s = children; for (uint8_t i=0; i < childrenCount; ++i) { @@ -383,7 +385,7 @@ inline void parseTrie(const uint8_t* start, const uint8_t* end, std::vector::iterator it=entries.begin(); it != entries.end(); ++it) output.push_back(it->entry); - delete cummulativeString; + delete [] cummulativeString; } diff --git a/src/ld/InputFiles.cpp b/src/ld/InputFiles.cpp index 839c5bc..ab31d4a 100644 --- a/src/ld/InputFiles.cpp +++ b/src/ld/InputFiles.cpp @@ -303,6 +303,7 @@ ld::File* InputFiles::makeFile(const Options::FileInfo& info, bool indirectDylib objOpts.srcKind = ld::relocatable::File::kSourceObj; objOpts.treateBitcodeAsData = _options.bitcodeKind() == Options::kBitcodeAsData; objOpts.usingBitcode = _options.bundleBitcode(); + objOpts.maxDefaultCommonAlignment = _options.maxDefaultCommonAlign(); ld::relocatable::File* objResult = mach_o::relocatable::parse(p, len, info.path, info.modTime, info.ordinal, objOpts); if ( objResult != NULL ) { @@ -318,7 +319,7 @@ ld::File* InputFiles::makeFile(const Options::FileInfo& info, bool indirectDylib OSAtomicIncrement32(&_totalObjectLoaded); return objResult; } - + // see if it is a dynamic library (or text-based dynamic library) ld::dylib::File* dylibResult; bool dylibsNotAllowed = false; @@ -582,12 +583,37 @@ void InputFiles::markExplicitlyLinkedDylibs() } } -bool InputFiles::libraryAlreadyLoaded(const char* path) +bool InputFiles::frameworkAlreadyLoaded(const char* path, const char* frameworkName) +{ + for (ld::File* file : _inputFiles) { + if ( strcmp(path, file->path()) == 0 ) + return true; + } + for (ld::dylib::File* dylibx : _allDylibs) { + const char* fname = dylibx->frameworkName(); + if ( fname == NULL ) + continue; + if ( strcmp(frameworkName, fname) == 0 ) + return true; + } + return false; +} + +bool InputFiles::libraryAlreadyLoaded(const char* path) { - for (std::vector::const_iterator it = _inputFiles.begin(); it != _inputFiles.end(); ++it) { - if ( strcmp(path, (*it)->path()) == 0 ) + for (ld::File* file : _inputFiles) { + if ( strcmp(path, file->path()) == 0 ) + return true; + } + for (ld::dylib::File* dylib : _allDylibs) { + if ( strcmp(path, dylib->path()) == 0 ) return true; } + for (const LibraryInfo& libInfo : _searchLibraries) { + if ( strcmp(path, libInfo.archive()->path()) == 0 ) + return true; + } + return false; } @@ -600,8 +626,10 @@ void InputFiles::addLinkerOptionLibraries(ld::Internal& state, ld::File::AtomHan // process frameworks specified in .o linker options for (CStringSet::const_iterator it = state.linkerOptionFrameworks.begin(); it != state.linkerOptionFrameworks.end(); ++it) { const char* frameworkName = *it; + if ( state.linkerOptionFrameworksProcessed.count(frameworkName) ) + continue; Options::FileInfo info = _options.findFramework(frameworkName); - if ( ! this->libraryAlreadyLoaded(info.path) ) { + if ( ! this->frameworkAlreadyLoaded(info.path, frameworkName) ) { info.ordinal = _linkerOptionOrdinal.nextLinkerOptionOrdinal(); try { ld::File* reader = this->makeFile(info, true); @@ -621,10 +649,13 @@ void InputFiles::addLinkerOptionLibraries(ld::Internal& state, ld::File::AtomHan warning("Auto-Linking supplied '%s', %s", info.path, msg); } } + state.linkerOptionFrameworksProcessed.insert(frameworkName); } // process libraries specified in .o linker options for (CStringSet::const_iterator it = state.linkerOptionLibraries.begin(); it != state.linkerOptionLibraries.end(); ++it) { const char* libName = *it; + if ( state.linkerOptionLibrariesProcessed.count(libName) ) + continue; Options::FileInfo info = _options.findLibrary(libName); if ( ! this->libraryAlreadyLoaded(info.path) ) { info.ordinal = _linkerOptionOrdinal.nextLinkerOptionOrdinal(); @@ -656,6 +687,7 @@ void InputFiles::addLinkerOptionLibraries(ld::Internal& state, ld::File::AtomHan warning("Auto-Linking supplied '%s', %s", info.path, msg); } } + state.linkerOptionLibrariesProcessed.insert(libName); } } @@ -1407,7 +1439,7 @@ void InputFiles::dylibs(ld::Internal& state) //fprintf(stderr, "all dylibs:\n"); //for(std::vector::iterator it=state.dylibs.begin(); it != state.dylibs.end(); ++it) { // const ld::dylib::File* dylib = *it; - // fprintf(stderr, " %p %s\n", dylib, dylib->path()); + // fprintf(stderr, " %p impl=%d %s\n", dylib, dylib->implicitlyLinked(), dylib->path()); //} // and -bundle_loader diff --git a/src/ld/InputFiles.h b/src/ld/InputFiles.h index 608ce39..e9927cd 100644 --- a/src/ld/InputFiles.h +++ b/src/ld/InputFiles.h @@ -97,7 +97,8 @@ private: void checkDylibClientRestrictions(ld::dylib::File*); void createOpaqueFileSections(); bool libraryAlreadyLoaded(const char* path); - + bool frameworkAlreadyLoaded(const char* path, const char* frameworkName); + // for pipelined linking void waitForInputFiles(); static void waitForInputFiles(InputFiles *inputFiles); @@ -144,9 +145,9 @@ private: LibraryInfo(ld::dylib::File* dylib) : _lib(dylib), _isDylib(true) {}; LibraryInfo(ld::archive::File* dylib) : _lib(dylib), _isDylib(false) {}; - bool isDylib() { return _isDylib; } - ld::dylib::File *dylib() { return (ld::dylib::File*)_lib; } - ld::archive::File *archive() { return (ld::archive::File*)_lib; } + bool isDylib() const { return _isDylib; } + ld::dylib::File *dylib() const { return (ld::dylib::File*)_lib; } + ld::archive::File *archive() const { return (ld::archive::File*)_lib; } }; std::vector _searchLibraries; }; diff --git a/src/ld/LinkEditClassic.hpp b/src/ld/LinkEditClassic.hpp index ee1c207..2eab13b 100644 --- a/src/ld/LinkEditClassic.hpp +++ b/src/ld/LinkEditClassic.hpp @@ -2002,7 +2002,6 @@ private: uint32_t symIndexOfLazyPointerAtom(const ld::Atom*); uint32_t symIndexOfNonLazyPointerAtom(const ld::Atom*); uint32_t symbolIndex(const ld::Atom*); - bool kextBundlesDontHaveIndirectSymbolTable(); std::vector _entries; @@ -2034,9 +2033,11 @@ uint32_t IndirectSymbolTableAtom::symIndexOfStubAtom(const ld::Atom* stubAtom { for (ld::Fixup::iterator fit = stubAtom->fixupsBegin(); fit != stubAtom->fixupsEnd(); ++fit) { if ( fit->binding == ld::Fixup::bindingDirectlyBound ) { - assert((fit->u.target->contentType() == ld::Atom::typeLazyPointer) - || (fit->u.target->contentType() == ld::Atom::typeLazyDylibPointer)); - return symIndexOfLazyPointerAtom(fit->u.target); + ld::Atom::ContentType type = fit->u.target->contentType(); + if (( type == ld::Atom::typeLazyPointer) || (type == ld::Atom::typeLazyDylibPointer) ) + return symIndexOfLazyPointerAtom(fit->u.target); + if ( type == ld::Atom::typeNonLazyPointer ) + return symIndexOfNonLazyPointerAtom(fit->u.target); } } throw "internal error: stub missing fixup to lazy pointer"; @@ -2152,12 +2153,6 @@ void IndirectSymbolTableAtom::encodeNonLazyPointerSection(ld::Internal::Final } } -template -bool IndirectSymbolTableAtom::kextBundlesDontHaveIndirectSymbolTable() -{ - return true; -} - template void IndirectSymbolTableAtom::encode() { @@ -2165,8 +2160,8 @@ void IndirectSymbolTableAtom::encode() if ( (this->_options.outputKind() == Options::kStaticExecutable) && !_options.positionIndependentExecutable() ) return; - // x86_64 kext bundles should not have an indirect symbol table - if ( (this->_options.outputKind() == Options::kKextBundle) && kextBundlesDontHaveIndirectSymbolTable() ) + // x86_64 kext bundles should not have an indirect symbol table unless using stubs + if ( (this->_options.outputKind() == Options::kKextBundle) && !this->_options.kextsUseStubs() ) return; // slidable static executables (-static -pie) should not have an indirect symbol table diff --git a/src/ld/Options.cpp b/src/ld/Options.cpp index c915f5e..3281f66 100644 --- a/src/ld/Options.cpp +++ b/src/ld/Options.cpp @@ -157,7 +157,7 @@ Options::Options(int argc, const char* argv[]) fUsingLazyDylibLinking(false), fEncryptable(true), fEncryptableForceOn(false), fEncryptableForceOff(false), fOrderData(true), fMarkDeadStrippableDylib(false), fMakeCompressedDyldInfo(true), fMakeCompressedDyldInfoForceOff(false), fNoEHLabels(false), - fAllowCpuSubtypeMismatches(false), fUseSimplifiedDylibReExports(false), + fAllowCpuSubtypeMismatches(false), fEnforceDylibSubtypesMatch(false), fUseSimplifiedDylibReExports(false), fObjCABIVersion2Override(false), fObjCABIVersion1Override(false), fCanUseUpwardDylib(false), fFullyLoadArchives(false), fLoadAllObjcObjectsFromArchives(false), fFlatNamespace(false), fLinkingMainExecutable(false), fForFinalLinkedImage(false), fForStatic(false), @@ -187,12 +187,14 @@ Options::Options(int argc, const char* argv[]) fSharedRegionEncodingV2(false), fUseDataConstSegment(false), fUseDataConstSegmentForceOn(false), fUseDataConstSegmentForceOff(false), fBundleBitcode(false), fHideSymbols(false), fVerifyBitcode(false), - fReverseMapUUIDRename(false), fReverseMapPath(NULL), fLTOCodegenOnly(false), + fReverseMapUUIDRename(false), fDeDupe(true), fVerboseDeDupe(false), + fReverseMapPath(NULL), fLTOCodegenOnly(false), fIgnoreAutoLink(false), fAllowDeadDups(false), fBitcodeKind(kBitcodeProcess), fPlatform(kPlatformUnknown), fDebugInfoStripping(kDebugInfoMinimal), fTraceOutputFile(NULL), fMacVersionMin(ld::macVersionUnset), fIOSVersionMin(ld::iOSVersionUnset), fSaveTempFiles(false), fSnapshotRequested(false), fPipelineFifo(NULL), - fDependencyInfoPath(NULL), fDependencyFileDescriptor(-1) + fDependencyInfoPath(NULL), fDependencyFileDescriptor(-1), fMaxDefaultCommonAlign(0), fFilePreference(kModTime), + fForceTextBasedStub(false) { this->checkForClassic(argc, argv); this->parsePreCommandLineEnvironmentSettings(); @@ -712,9 +714,8 @@ Options::FileInfo Options::findLibrary(const char* rootName, bool dylibsOnly) co it != fLibrarySearchPaths.end(); it++) { const char* dir = *it; - if ( checkForFile("%s/lib%s.tbd", dir, rootName, result) ) - return result; - if ( checkForFile("%s/lib%s.dylib", dir, rootName, result) ) + auto path = std::string(dir) + "/lib" + rootName + ".dylib"; + if ( findFile(path, {".tbd"}, result) ) return result; } for (std::vector::const_iterator it = fLibrarySearchPaths.begin(); @@ -743,9 +744,8 @@ Options::FileInfo Options::findLibrary(const char* rootName, bool dylibsOnly) co it != fLibrarySearchPaths.end(); it++) { const char* dir = *it; - if ( lookForDylibs && checkForFile("%s/lib%s.tbd", dir, rootName, result) ) - return result; - if ( lookForDylibs && checkForFile("%s/lib%s.dylib", dir, rootName, result) ) + auto path = std::string(dir) + "/lib" + rootName + ".dylib"; + if ( lookForDylibs && findFile(path, {".tbd"}, result) ) return result; if ( lookForDylibs && checkForFile("%s/lib%s.so", dir, rootName, result) ) return result; @@ -785,15 +785,8 @@ Options::FileInfo Options::findFramework(const char* rootName, const char* suffi possiblePath = std::string(realPath).append(suffix); } FileInfo result; - bool found = result.checkFileExists(*this, (possiblePath + ".tbd").c_str()); - if ( !found ) - found = result.checkFileExists(*this, possiblePath.c_str()); - if ( fTraceDylibSearching ) - printf("[Logging for XBS]%sfound framework: '%s'\n", - (found ? " " : " not "), possiblePath.c_str()); - if ( found ) { + if ( findFile(possiblePath, {".tbd"}, result) ) return result; - } } // try without suffix if ( suffix != NULL ) @@ -802,41 +795,88 @@ Options::FileInfo Options::findFramework(const char* rootName, const char* suffi throwf("framework not found %s", rootName); } +static std::string replace_extension(const std::string &path, const std::string &ext) +{ + auto result = path; + auto lastSlashIdx = result.find_last_of('/'); + auto lastDotIdx = result.find_last_of('.'); + if (lastDotIdx != std::string::npos && lastDotIdx > lastSlashIdx) + result.erase(lastDotIdx, std::string::npos); + if ( ext.size() > 0 && ext[0] == '.' ) + result.append(ext); + else + result.append('.' + ext); + return result; +} + +bool Options::findFile(const std::string &path, const std::vector &tbdExtensions, FileInfo& result) const +{ + FileInfo tbdInfo; + for ( const auto &ext : tbdExtensions ) { + auto newPath = replace_extension(path, ext); + bool found = tbdInfo.checkFileExists(*this, newPath.c_str()); + if ( fTraceDylibSearching ) + printf("[Logging for XBS]%sfound library: '%s'\n", (found ? " " : " not "), newPath.c_str()); + if ( found ) { + if ( (fFilePreference == kTextBasedStub) || fForceTextBasedStub ) { + result = tbdInfo; + return true; + } else { + break; + } + } + } + + FileInfo dylibInfo; + { + bool found = dylibInfo.checkFileExists(*this, path.c_str()); + if ( fTraceDylibSearching ) + printf("[Logging for XBS]%sfound library: '%s'\n", (found ? " " : " not "), path.c_str()); + if ( found && (fFilePreference == kMachO) ) { + result = dylibInfo; + return true; + } + } + + if ( !dylibInfo.missing() && tbdInfo.missing() ) { + result = dylibInfo; + return true; + } + else if ( dylibInfo.missing() && !tbdInfo.missing() ) { + result = tbdInfo; + return true; + } + else if ( !dylibInfo.missing() && !tbdInfo.missing() ) { + if ( dylibInfo.modTime == tbdInfo.modTime ) { + result = tbdInfo; + return true; + } + else { + // Disable false warning about out-of-sync .tbd files. + //warning("text-based stub file %s and library file %s are out of sync. Falling back to library file for linking.", tbdInfo.path, dylibInfo.path); + result = dylibInfo; + return true; + } + } + + return false; +} + Options::FileInfo Options::findFile(const std::string &path) const { FileInfo result; // if absolute path and not a .o file, then use SDK prefix if ( (path[0] == '/') && (strcmp(&path[path.size()-2], ".o") != 0) ) { - auto tbdFile = path; - auto lastSlashIdx = tbdFile.find_last_of('/'); - auto lastDotIdx = tbdFile.find_last_of('.'); - if (lastDotIdx != std::string::npos && lastDotIdx > lastSlashIdx) - tbdFile.erase(lastDotIdx, std::string::npos); - tbdFile.append(".tbd"); - for (const auto* sdkPathDir : fSDKPaths) { - auto possiblePath = std::string(sdkPathDir) + tbdFile; - if ( result.checkFileExists(*this, possiblePath.c_str()) ) - return result; - possiblePath = std::string(sdkPathDir) + path; - if ( result.checkFileExists(*this, possiblePath.c_str()) ) + auto possiblePath = std::string(sdkPathDir) + path; + if ( findFile(possiblePath, {".tbd"}, result) ) return result; } } // try raw path - { - std::string file = path; - auto lastDotIdx = file.find_last_of('.'); - if (lastDotIdx != std::string::npos) - file.erase(lastDotIdx, std::string::npos); - if ( result.checkFileExists(*this, file.append(".tbd").c_str()) ) - return result; - } - if ( result.checkFileExists(*this, path.c_str()) ) { + if ( findFile(path, {".tbd"}, result) ) return result; - } - // try @executable_path substitution if ( (path.find("@executable_path/") == 0) && (fExecutablePath != nullptr) ) { @@ -848,16 +888,8 @@ Options::FileInfo Options::findFile(const std::string &path) const else strcpy(newPath, &path[17]); - std::string file = newPath; - auto lastDotIdx = file.find_last_of('.'); - if (lastDotIdx != std::string::npos) - file.erase(lastDotIdx, std::string::npos); - if ( result.checkFileExists(*this, file.append(".tbd").c_str()) ) { - return result; - } - if ( result.checkFileExists(*this, newPath) ) { + if ( findFile(newPath, {".tbd"}, result) ) return result; - } } // not found @@ -893,9 +925,7 @@ Options::FileInfo Options::findFileUsingPaths(const std::string &path) const auto leafPath = path.substr(beginPos); for (const auto* dir : fFrameworkSearchPaths) { auto possiblePath = dir + leafPath; - if ( checkForFile("%s.%s", possiblePath.c_str(), "tbd", result) ) - return result; - if ( checkForFile("%s", possiblePath.c_str(), "", result) ) + if ( findFile(possiblePath, {".tbd"}, result) ) return result; } } else { @@ -907,9 +937,8 @@ Options::FileInfo Options::findFileUsingPaths(const std::string &path) const if ( !embeddedDylib ) { for (const auto* dir : fLibrarySearchPaths) { //fprintf(stderr,"Finding Library: %s/%s\n", dir, leafName); - if ( checkForFile("%s/%s", dir, std::string(leafName).append(".tbd").c_str(), result) ) - return result; - if ( checkForFile("%s/%s", dir, leafName.c_str(), result) ) + auto possiblePath = dir + leafName; + if ( findFile(possiblePath, {".tbd"}, result) ) return result; } } @@ -1395,58 +1424,32 @@ Options::Treatment Options::parseTreatment(const char* treatment) void Options::setMacOSXVersionMin(const char* version) { - if ( version == NULL ) - throw "-macosx_version_min argument missing"; - - if ( (strncmp(version, "10.", 3) == 0) && isdigit(version[3]) ) { - unsigned int minorVersion = 0; - for (int i=3; isdigit(version[i]); ++i) { - minorVersion = minorVersion*10 + (version[i] - '0'); - } - if ( minorVersion > 255 ) { - warning("Mac OS X minor version > 255 in '%s'", version); - minorVersion = 255; - } - fMacVersionMin = (ld::MacVersionMin)(0x000A0000 | (minorVersion << 8)); - fPlatform = kPlatformOSX; - } - else { - warning("unknown option to -macosx_version_min, not 10.x"); + uint32_t value; + if ( !parsePackedVersion32(version, value) ) { + throwf("-macosx_version_min value malformed: '%s'", version); } + fMacVersionMin = (ld::MacVersionMin)value; + fPlatform = kPlatformOSX; } void Options::setIOSVersionMin(const char* version) { - if ( version == NULL ) - throw "-ios_version_min argument missing"; - if ( ! isdigit(version[0]) ) - throw "-ios_version_min argument is not a number"; - if ( version[1] != '.' ) - throw "-ios_version_min argument is missing period as second character"; - if ( ! isdigit(version[2]) ) - throw "-ios_version_min argument is not a number"; - - unsigned int majorVersion = version[0] - '0'; - unsigned int minorVersion = version[2] - '0'; - fIOSVersionMin = (ld::IOSVersionMin)((majorVersion << 16) | (minorVersion << 8)); + uint32_t value; + if ( !parsePackedVersion32(version, value) ) { + throwf("-ios_version_min value malformed: '%s'", version); + } + fIOSVersionMin = (ld::IOSVersionMin)value; fPlatform = kPlatformiOS; } void Options::setWatchOSVersionMin(const char* version) { - if ( version == NULL ) - throw "-watchos_version_min argument missing"; - if ( ! isdigit(version[0]) ) - throw "-watchos_version_min argument is not a number"; - if ( version[1] != '.' ) - throw "-watchos_version_min argument is missing period as second character"; - if ( ! isdigit(version[2]) ) - throw "-watchos_version_min argument is not a number"; - - unsigned int majorVersion = version[0] - '0'; - unsigned int minorVersion = version[2] - '0'; - fWatchOSVersionMin = (ld::WatchOSVersionMin)((majorVersion << 16) | (minorVersion << 8)); + uint32_t value; + if ( !parsePackedVersion32(version, value) ) { + throwf("-watchos_version_min value malformed: '%s'", version); + } + fWatchOSVersionMin = (ld::WatchOSVersionMin)value; fPlatform = kPlatformWatchOS; } @@ -2027,6 +2030,73 @@ std::string Options::getVersionString64(uint64_t ver) const return versionString.str(); } +// Convert X.Y[.Z] to 32-bit value xxxxyyzz +bool Options::parsePackedVersion32(const std::string& versionStr, uint32_t &result) +{ + result = 0; + + if ( versionStr.empty() ) + return false; + + size_t pos = versionStr.find('.'); + if ( pos == std::string::npos ) + return false; + + std::string majorStr = versionStr.substr(0, pos); + std::string rest = versionStr.substr(pos+1); + + try { + size_t majorEnd; + int majorValue = std::stoi(majorStr, &majorEnd); + if ( majorEnd != majorStr.size() ) + return false; + if ( majorValue < 0 ) + return false; + if ( majorValue > 65535 ) + return false; + + std::string minorStr; + std::string microStr; + pos = rest.find('.'); + if ( pos == std::string::npos ) { + minorStr = rest; + } + else { + minorStr = rest.substr(0, pos); + microStr = rest.substr(pos+1); + } + + size_t minorEnd; + int minorValue = std::stoi(minorStr, &minorEnd); + if ( minorEnd != minorStr.size() ) + return false; + if ( minorValue < 0 ) + return false; + if ( minorValue > 255 ) + return false; + + int microValue = 0; + if ( !microStr.empty() ) { + size_t microEnd; + microValue = std::stoi(microStr, µEnd); + if ( microEnd != microStr.size() ) + return false; + if ( microValue < 0 ) + return false; + if ( microValue > 255 ) + return false; + } + + result = (majorValue << 16) | (minorValue << 8) | microValue; + + return true; + } + catch (...) { + // std::stoi() throws exception on malformed input + return false; + } +} + std::string Options::getSDKVersionStr() const { return getVersionString32(fSDKVersion); @@ -2695,7 +2765,10 @@ void Options::parse(int argc, const char* argv[]) throw "-segprot missing segName max-prot init-prot"; seg.max = parseProtection(argv[++i]); seg.init = parseProtection(argv[++i]); - fCustomSegmentProtections.push_back(seg); + if ( strcmp(seg.name, "__LINKEDIT") == 0 ) + warning("-segprot cannot be used to modify __LINKEDIT protections"); + else + fCustomSegmentProtections.push_back(seg); cannotBeUsedWithBitcode(arg); } else if ( strcmp(arg, "-pagezero_size") == 0 ) { @@ -2721,9 +2794,6 @@ void Options::parse(int argc, const char* argv[]) if ( size == NULL ) throw "-stack_size missing
"; fStackSize = parseAddress(size); - uint64_t temp = fStackSize & (-4096); // page align - if ( (fStackSize != temp) ) - warning("-stack_size not page aligned, rounding down"); } else if ( strcmp(arg, "-allow_stack_execute") == 0 ) { fExecutableStack = true; @@ -2766,6 +2836,8 @@ void Options::parse(int argc, const char* argv[]) // Use this flag to set default behavior for deployement targets. else if ( strcmp(arg, "-macosx_version_min") == 0 ) { const char* macVers = argv[++i]; + if ( macVers == NULL ) + throw "-macosx_version_min missing version argument"; const char* envMacVers = getenv("MACOSX_DEPLOYMENT_TARGET"); const char* enviPhoneVers = getenv("IPHONEOS_DEPLOYMENT_TARGET"); if ( (envMacVers != NULL) && (enviPhoneVers != NULL) ) { @@ -2788,26 +2860,44 @@ void Options::parse(int argc, const char* argv[]) } } else if ( (strcmp(arg, "-ios_version_min") == 0) || (strcmp(arg, "-iphoneos_version_min") == 0) ) { - setIOSVersionMin(argv[++i]); + const char* vers = argv[++i]; + if ( vers == NULL ) + throw "-ios_version_min missing version argument"; + setIOSVersionMin(vers); } else if ( strcmp(arg, "-ios_simulator_version_min") == 0 ) { - setIOSVersionMin(argv[++i]); + const char* vers = argv[++i]; + if ( vers == NULL ) + throw "-ios_simulator_version_min missing version argument"; + setIOSVersionMin(vers); fTargetIOSSimulator = true; } else if ( strcmp(arg, "-watchos_version_min") == 0 ) { - setWatchOSVersionMin(argv[++i]); + const char* vers = argv[++i]; + if ( vers == NULL ) + throw "-watchos_version_min missing version argument"; + setWatchOSVersionMin(vers); } else if ( strcmp(arg, "-watchos_simulator_version_min") == 0 ) { - setWatchOSVersionMin(argv[++i]); + const char* vers = argv[++i]; + if ( vers == NULL ) + throw "-watchos_simulator_version_min missing version argument"; + setWatchOSVersionMin(vers); fTargetIOSSimulator = true; } #if SUPPORT_APPLE_TV else if ( strcmp(arg, "-tvos_version_min") == 0 ) { - setIOSVersionMin(argv[++i]); + const char* vers = argv[++i]; + if ( vers == NULL ) + throw "-tvos_version_min missing version argument"; + setIOSVersionMin(vers); fPlatform = kPlatform_tvOS; } else if ( strcmp(arg, "-tvos_simulator_version_min") == 0 ) { - setIOSVersionMin(argv[++i]); + const char* vers = argv[++i]; + if ( vers == NULL ) + throw "-tvos_simulator_version_min missing version argument"; + setIOSVersionMin(vers); fPlatform = kPlatform_tvOS; fTargetIOSSimulator = true; } @@ -3627,6 +3717,43 @@ void Options::parse(int argc, const char* argv[]) fUseDataConstSegmentForceOff = true; cannotBeUsedWithBitcode(arg); } + else if ( strcmp(arg, "-no_deduplicate") == 0 ) { + fDeDupe = false; + } + else if ( strcmp(arg, "-verbose_deduplicate") == 0 ) { + fVerboseDeDupe = true; + } + else if ( strcmp(arg, "-max_default_common_align") == 0 ) { + const char* alignStr = argv[++i]; + if ( alignStr == NULL ) + throw "-max_default_common_align missing "; + // argument is a hexadecimal number + char* endptr; + unsigned long value = strtoul(alignStr, &endptr, 16); + if ( *endptr != '\0') + throw "argument for -max_default_common_align is not a hexadecimal number"; + if ( value > 0x8000 ) + throw "argument for -max_default_common_align must be less than or equal to 0x8000"; + if ( value == 0 ) { + warning("zero is not a valid -max_default_common_align"); + value = 1; + } + // alignment is power of 2 + uint8_t alignment = (uint8_t)__builtin_ctz(value); + if ( (unsigned long)(1 << alignment) != value ) { + warning("alignment for -max_default_common_align is not a power of two, using 0x%X", 1 << alignment); + } + fMaxDefaultCommonAlign = alignment; + } + else if ( strcmp(arg, "-prefer-mod-time-check") == 0 ) { + fFilePreference = kModTime; + } + else if ( strcmp(arg, "-prefer-text-based-stub-file") == 0 ) { + fFilePreference = kTextBasedStub; + } + else if ( strcmp(arg, "-prefer-macho-file") == 0 ) { + fFilePreference = kMachO; + } // put this last so that it does not interfer with other options starting with 'i' else if ( strncmp(arg, "-i", 2) == 0 ) { const char* colon = strchr(arg, ':'); @@ -3701,16 +3828,7 @@ void Options::buildSearchPaths(int argc, const char* argv[]) } if ( libSearchDir[0] == '\0' ) throw "-L must be immediately followed by a directory path (no space)"; - struct stat statbuf; - if ( stat(libSearchDir, &statbuf) == 0 ) { - if ( statbuf.st_mode & S_IFDIR ) - libraryPaths.push_back(libSearchDir); - else - warning("path '%s' following -L not a directory", libSearchDir); - } - else { - warning("directory not found for option '-L%s'", libSearchDir); - } + libraryPaths.push_back(libSearchDir); } else if ( (argv[i][0] == '-') && (argv[i][1] == 'F') ) { const char* frameworkSearchDir = &argv[i][2]; @@ -3724,16 +3842,7 @@ void Options::buildSearchPaths(int argc, const char* argv[]) } if ( frameworkSearchDir[0] == '\0' ) throw "-F must be immediately followed by a directory path (no space)"; - struct stat statbuf; - if ( stat(frameworkSearchDir, &statbuf) == 0 ) { - if ( statbuf.st_mode & S_IFDIR ) - frameworkPaths.push_back(frameworkSearchDir); - else - warning("path '%s' following -F not a directory", frameworkSearchDir); - } - else { - warning("directory not found for option '-F%s'", frameworkSearchDir); - } + frameworkPaths.push_back(frameworkSearchDir); } else if ( strcmp(argv[i], "-Z") == 0 ) addStandardLibraryDirectories = false; @@ -3820,8 +3929,13 @@ void Options::buildSearchPaths(int argc, const char* argv[]) strcat(newPath, libDir); struct stat statBuffer; if ( stat(newPath, &statBuffer) == 0 ) { - fLibrarySearchPaths.push_back(strdup(newPath)); - sdkOverride = true; + if ( (statBuffer.st_mode & S_IFDIR) == 0 ) { + warning("-syslibroot and -L combined path '%s' is not a directory", newPath); + } + else { + fLibrarySearchPaths.push_back(strdup(newPath)); + sdkOverride = true; + } } } } @@ -3831,11 +3945,21 @@ void Options::buildSearchPaths(int argc, const char* argv[]) // if one SDK is specified and a standard library path is not in the SDK, don't use it } else { - fLibrarySearchPaths.push_back(libDir); + struct stat statBuffer; + if ( stat(libDir, &statBuffer) == 0 ) { + if ( (statBuffer.st_mode & S_IFDIR) == 0 ) + warning("-L path '%s' is not a directory", libDir); + else + fLibrarySearchPaths.push_back(libDir); + } + else if ( !addStandardLibraryDirectories || (strcmp(libDir, "/usr/local/lib") != 0) ) { + warning("directory not found for option '-L%s'", libDir); + } } } } + // now merge sdk and framework paths to make real search paths fFrameworkSearchPaths.reserve(frameworkPaths.size()*(fSDKPaths.size()+1)); int frameIndex = 0; @@ -3859,8 +3983,13 @@ void Options::buildSearchPaths(int argc, const char* argv[]) strcat(newPath, frameworkDir); struct stat statBuffer; if ( stat(newPath, &statBuffer) == 0 ) { - fFrameworkSearchPaths.push_back(strdup(newPath)); - sdkOverride = true; + if ( (statBuffer.st_mode & S_IFDIR) == 0 ) { + warning("-syslibroot and -F combined path '%s' is not a directory", newPath); + } + else { + fFrameworkSearchPaths.push_back(strdup(newPath)); + sdkOverride = true; + } } } } @@ -3870,7 +3999,16 @@ void Options::buildSearchPaths(int argc, const char* argv[]) // if one SDK is specified and a standard library path is not in the SDK, don't use it } else { - fFrameworkSearchPaths.push_back(frameworkDir); + struct stat statBuffer; + if ( stat(frameworkDir, &statBuffer) == 0 ) { + if ( (statBuffer.st_mode & S_IFDIR) == 0 ) + warning("-F path '%s' is not a directory", frameworkDir); + else + fFrameworkSearchPaths.push_back(frameworkDir); + } + else if ( !addStandardLibraryDirectories || (strcmp(frameworkDir, "/Library/Frameworks/") != 0) ) { + warning("directory not found for option '-F%s'", frameworkDir); + } } } } @@ -3932,6 +4070,9 @@ void Options::parsePreCommandLineEnvironmentSettings() if (getenv("LD_ALLOW_CPU_SUBTYPE_MISMATCHES") != NULL) fAllowCpuSubtypeMismatches = true; + if (getenv("LD_DYLIB_CPU_SUBTYPES_MUST_MATCH") != NULL) + fEnforceDylibSubtypesMatch = true; + sWarningsSideFilePath = getenv("LD_WARN_FILE"); const char* customDyldPath = getenv("LD_DYLD_PATH"); @@ -3950,6 +4091,10 @@ void Options::parsePreCommandLineEnvironmentSettings() if (pipeFdString != NULL) { fPipelineFifo = pipeFdString; } + + // Workaround for rdar://problem/24301175 + if ((getenv("RC_XBS") != NULL) && !(getenv("RC_BUILDIT") != NULL)) + fForceTextBasedStub = true; } @@ -4521,10 +4666,13 @@ void Options::reconfigureDefaults() // only ARM and x86_64 enforces that cpu-sub-types must match switch ( fArchitecture ) { case CPU_TYPE_ARM: + break; case CPU_TYPE_X86_64: + fEnforceDylibSubtypesMatch = false; break; case CPU_TYPE_I386: case CPU_TYPE_ARM64: + fEnforceDylibSubtypesMatch = false; fAllowCpuSubtypeMismatches = true; break; } @@ -4621,6 +4769,12 @@ void Options::reconfigureDefaults() else if ( (fArchitecture == CPU_TYPE_ARM) && min_iOS(ld::iOS_9_0) ) { fTLVSupport = true; } + else if ( fTargetIOSSimulator && (fArchitecture == CPU_TYPE_X86_64) && min_iOS(ld::iOS_8_0) ) { + fTLVSupport = true; + } + else if ( fTargetIOSSimulator && (fArchitecture == CPU_TYPE_I386) && min_iOS(ld::iOS_9_0) ) { + fTLVSupport = true; + } // default to adding version load command for dynamic code, static code must opt-in switch ( fOutputKind ) { @@ -4869,6 +5023,13 @@ void Options::reconfigureDefaults() } } + // Reduce the default alignment of structures/arrays to save memory in embedded systems + if ( fMaxDefaultCommonAlign == 0 ) { + if ( fOutputKind == Options::kPreload ) + fMaxDefaultCommonAlign = 8; + else + fMaxDefaultCommonAlign = 15; + } } void Options::checkIllegalOptionCombinations() @@ -4876,9 +5037,24 @@ void Options::checkIllegalOptionCombinations() // check -undefined setting switch ( fUndefinedTreatment ) { case kUndefinedError: - case kUndefinedDynamicLookup: // always legal break; + case kUndefinedDynamicLookup: + switch (fPlatform) { + case kPlatformOSX: + break; + case kPlatformiOS: + case kPlatformWatchOS: + #if SUPPORT_APPLE_TV + case kPlatform_tvOS: + #endif + if ( fOutputKind != kKextBundle ) + warning("-undefined dynamic_lookup is deprecated on %s", platformName(fPlatform)); + break; + default: + break; + } + break; case kUndefinedWarning: case kUndefinedSuppress: // requires flat namespace @@ -4896,7 +5072,11 @@ void Options::checkIllegalOptionCombinations() const char* lastSlash = strrchr(info.path, '/'); if ( lastSlash == NULL ) lastSlash = info.path - 1; - if ( strcmp(&lastSlash[1], subUmbrella) == 0 ) { + std::string path(&lastSlash[1]); + auto idx = path.find(".tbd", path.size() - 4); + if (idx != std::string::npos) + path.erase(idx); + if ( path == subUmbrella ) { info.options.fReExport = true; found = true; fLinkSnapshot.recordSubUmbrella(info.path); @@ -4931,8 +5111,23 @@ void Options::checkIllegalOptionCombinations() } // sync reader options - if ( fNameSpace != kTwoLevelNameSpace ) + if ( fNameSpace != kTwoLevelNameSpace ) { fFlatNamespace = true; + switch (fPlatform) { + case kPlatformOSX: + break; + case kPlatformiOS: + case kPlatformWatchOS: + #if SUPPORT_APPLE_TV + case Options::kPlatform_tvOS: + #endif + warning("-flat_namespace is deprecated on %s", platformName(fPlatform)); + break; + default: + break; + } + } + // check -stack_addr if ( fStackAddr != 0 ) { @@ -4956,37 +5151,54 @@ void Options::checkIllegalOptionCombinations() if ( fStackSize != 0 ) { switch (fArchitecture) { case CPU_TYPE_I386: - if ( fStackSize > 0xFFFFFFFF ) - throw "-stack_size must be < 4G for 32-bit processes"; - if ( fStackAddr == 0 ) { - fStackAddr = 0xC0000000; + if ( fPlatform == kPlatformOSX ) { + if ( fStackSize > 0xFFFFFFFF ) + throw "-stack_size must be < 4GB for 32-bit processes"; + if ( fStackAddr == 0 ) + fStackAddr = 0xC0000000; + if ( (fStackAddr > 0xB0000000) && ((fStackAddr-fStackSize) < 0xB0000000) ) + warning("custom stack placement overlaps and will disable shared region"); + } + else { + if ( fStackSize > 0x1F000000 ) + throw "-stack_size must be < 496MB"; + if ( fStackAddr == 0 ) + fStackAddr = 0xC0000000; } - if ( (fStackAddr > 0xB0000000) && ((fStackAddr-fStackSize) < 0xB0000000) ) - warning("custom stack placement overlaps and will disable shared region"); break; case CPU_TYPE_ARM: - if ( fStackSize > 0x2F000000 ) - throw "-stack_size must be < 752MB"; + if ( fStackSize > 0x1F000000 ) + throw "-stack_size must be < 496MB"; if ( fStackAddr == 0 ) - fStackAddr = 0x2F000000; - if ( fStackAddr > 0x30000000) - throw "-stack_addr must be < 0x30000000 for arm"; + fStackAddr = 0x1F000000; + if ( fStackAddr > 0x20000000) + throw "-stack_addr must be < 0x20000000 for arm"; break; case CPU_TYPE_X86_64: - if ( fStackAddr == 0 ) { - fStackAddr = 0x00007FFF5C000000LL; + if ( fPlatform == kPlatformOSX ) { + if ( fStackSize > 0x10000000000 ) + throw "-stack_size must be <= 1TB"; + if ( fStackAddr == 0 ) { + fStackAddr = 0x00007FFF5C000000LL; + } + } + else { + if ( fStackSize > 0x20000000 ) + throw "-stack_size must be <= 512MB"; + if ( fStackAddr == 0 ) { + fStackAddr = 0x120000000; } break; case CPU_TYPE_ARM64: if ( fStackSize > 0x20000000 ) - throw "-stack_size must be < 512MB"; - if ( fStackAddr == 0 ) { + throw "-stack_size must be <= 512MB"; + if ( fStackAddr == 0 ) fStackAddr = 0x120000000; } break; } - if ( (fStackSize & -4096) != fStackSize ) - throw "-stack_size must be multiples of 4K"; + if ( (fStackSize & (-fSegmentAlignment)) != fStackSize ) + throwf("-stack_size must be multiple of segment alignment (%lldKB)", fSegmentAlignment/1024); switch ( fOutputKind ) { case Options::kDynamicExecutable: case Options::kStaticExecutable: @@ -5313,15 +5525,6 @@ void Options::checkIllegalOptionCombinations() if ( !fSegmentOrder.empty() && (fOutputKind != Options::kPreload) ) throw "-segment_order can only used used with -preload output"; - if ( fBitcodeKind != kBitcodeProcess && - fOutputKind != Options::kObjectFile ) { - throw "-bitcode_process_mode can only be used together with -r"; - } - // auto fix up the process type for strip -S. - // when there is only one input and output type is object file, downgrade kBitcodeProcess to kBitcodeAsData. - if ( fOutputKind == Options::kObjectFile && fInputFiles.size() == 1 && fBitcodeKind == Options::kBitcodeProcess ) - fBitcodeKind = Options::kBitcodeAsData; - // warn about bitcode option combinations if ( !fBundleBitcode ) { if ( fVerifyBitcode ) @@ -5332,9 +5535,17 @@ void Options::checkIllegalOptionCombinations() if ( fReverseMapPath != NULL && !fHideSymbols ) { throw "-bitcode_symbol_map can only be used with -bitcode_hide_symbols"; } + if ( fBitcodeKind != kBitcodeProcess && + fOutputKind != Options::kObjectFile ) { + throw "-bitcode_process_mode can only be used together with -r"; + } + // auto fix up the process type for strip -S. + // when there is only one input and output type is object file, downgrade kBitcodeProcess to kBitcodeAsData. + if ( fOutputKind == Options::kObjectFile && fInputFiles.size() == 1 && fBitcodeKind == Options::kBitcodeProcess ) + fBitcodeKind = Options::kBitcodeAsData; // warn if building an embedded iOS dylib for pre-iOS 8 - // How can we suppress "ld: warning: embedded dylibs/frameworks only run on iOS 8 or laterÓ when building XCTest? + // How can we suppress "ld: warning: embedded dylibs/frameworks only run on iOS 8 or later" when building XCTest? if ( (fOutputKind == Options::kDynamicLibrary) && (fIOSVersionMin != ld::iOSVersionUnset) && (fDylibInstallName != NULL) ) { if ( !min_iOS(ld::iOS_8_0) && (fDylibInstallName[0] == '@') && !fEncryptableForceOff ) warning("embedded dylibs/frameworks only run on iOS 8 or later"); diff --git a/src/ld/Options.h b/src/ld/Options.h index e7ffff2..5155de4 100644 --- a/src/ld/Options.h +++ b/src/ld/Options.h @@ -147,6 +147,18 @@ public: // the source, which dies with the stack frame. FileInfo(FileInfo const &other) : path(other.path), fileLen(other.fileLen), modTime(other.modTime), options(other.options), ordinal(other.ordinal), fromFileList(other.fromFileList), inputFileSlot(-1) { ((FileInfo&)other).path = NULL; }; + FileInfo &operator=(FileInfo other) { + std::swap(path, other.path); + std::swap(fileLen, other.fileLen); + std::swap(modTime, other.modTime); + std::swap(options, other.options); + std::swap(ordinal, other.ordinal); + std::swap(fromFileList, other.fromFileList); + std::swap(inputFileSlot, other.inputFileSlot); + std::swap(readyToParse, other.readyToParse); + return *this; + } + // Create an empty FileInfo. The path can be set implicitly by checkFileExists(). FileInfo() : path(NULL), fileLen(0), modTime(0), options(), fromFileList(false) {}; @@ -248,6 +260,7 @@ public: bool preferSubArchitecture() const { return fHasPreferredSubType; } cpu_subtype_t subArchitecture() const { return fSubArchitecture; } bool allowSubArchitectureMismatches() const { return fAllowCpuSubtypeMismatches; } + bool enforceDylibSubtypesMatch() const { return fEnforceDylibSubtypesMatch; } bool forceCpuSubtypeAll() const { return fForceSubtypeAll; } const char* architectureName() const { return fArchitectureName; } void setArchitecture(cpu_type_t, cpu_subtype_t subtype, Options::Platform platform); @@ -313,6 +326,7 @@ public: bool warnCommons() const { return fWarnCommons; } bool keepRelocations(); FileInfo findFile(const std::string &path) const; + bool findFile(const std::string &path, const std::vector &tbdExtensions, FileInfo& result) const; UUIDMode UUIDMode() const { return fUUIDMode; } bool warnStabs(); bool pauseAtEnd() { return fPause; } @@ -415,6 +429,8 @@ public: bool hideSymbols() const { return fHideSymbols; } bool verifyBitcode() const { return fVerifyBitcode; } bool renameReverseSymbolMap() const { return fReverseMapUUIDRename; } + bool deduplicateFunctions() const { return fDeDupe; } + bool verboseDeduplicate() const { return fVerboseDeDupe; } const char* reverseSymbolMapPath() const { return fReverseMapPath; } std::string reverseMapTempPath() const { return fReverseMapTempPath; } bool ltoCodegenOnly() const { return fLTOCodegenOnly; } @@ -458,6 +474,9 @@ public: std::vector writeBitcodeLinkOptions() const; std::string getSDKVersionStr() const; std::string getPlatformStr() const; + uint8_t maxDefaultCommonAlign() const { return fMaxDefaultCommonAlign; } + + static uint32_t parseVersionNumber32(const char*); private: typedef std::unordered_map NameToOrder; @@ -465,6 +484,7 @@ private: enum ExportMode { kExportDefault, kExportSome, kDontExportSome }; enum LibrarySearchMode { kSearchDylibAndArchiveInEachDir, kSearchAllDirsForDylibsThenAllDirsForArchives }; enum InterposeMode { kInterposeNone, kInterposeAllExternal, kInterposeSome }; + enum FilePreference { kModTime, kTextBasedStub, kMachO }; class SetWithWildcards { public: @@ -500,9 +520,9 @@ private: bool checkForFile(const char* format, const char* dir, const char* rootName, FileInfo& result) const; uint64_t parseVersionNumber64(const char*); - uint32_t parseVersionNumber32(const char*); std::string getVersionString32(uint32_t ver) const; std::string getVersionString64(uint64_t ver) const; + bool parsePackedVersion32(const std::string& versionStr, uint32_t &result); void parseSectionOrderFile(const char* segment, const char* section, const char* path); void parseOrderFile(const char* path, bool cstring); void addSection(const char* segment, const char* section, const char* path); @@ -639,6 +659,7 @@ private: bool fMakeCompressedDyldInfoForceOff; bool fNoEHLabels; bool fAllowCpuSubtypeMismatches; + bool fEnforceDylibSubtypesMatch; bool fUseSimplifiedDylibReExports; bool fObjCABIVersion2Override; bool fObjCABIVersion1Override; @@ -715,6 +736,8 @@ private: bool fHideSymbols; bool fVerifyBitcode; bool fReverseMapUUIDRename; + bool fDeDupe; + bool fVerboseDeDupe; const char* fReverseMapPath; std::string fReverseMapTempPath; bool fLTOCodegenOnly; @@ -758,6 +781,9 @@ private: const char* fPipelineFifo; const char* fDependencyInfoPath; mutable int fDependencyFileDescriptor; + uint8_t fMaxDefaultCommonAlign; + FilePreference fFilePreference; + bool fForceTextBasedStub; }; diff --git a/src/ld/OutputFile.cpp b/src/ld/OutputFile.cpp index dd0e5b2..1d708da 100644 --- a/src/ld/OutputFile.cpp +++ b/src/ld/OutputFile.cpp @@ -1294,7 +1294,7 @@ static bool isPageOffsetKind(const ld::Fixup* fixup, bool mustBeGOT=false) #define LOH_ASSERT(cond) \ if ( !(cond) ) { \ - warning("ignoring linker optimzation hint at %s+0x%X because " #cond, atom->name(), fit->offsetInAtom); \ + warning("ignoring linker optimization hint at %s+0x%X because " #cond, atom->name(), fit->offsetInAtom); \ break; \ } @@ -2235,7 +2235,6 @@ void OutputFile::applyFixUps(ld::Internal& state, uint64_t mhAddress, const ld:: LOH_ASSERT(isADRP); isLDR = parseLoadOrStore(infoC.instruction, ldrInfoC); LOH_ASSERT(isLDR); - LOH_ASSERT(ldrInfoC.offset == 0); isADD = parseADD(infoB.instruction, addInfoB); isLDR = parseLoadOrStore(infoB.instruction, ldrInfoB); if ( isLDR ) { @@ -2244,8 +2243,8 @@ void OutputFile::applyFixUps(ld::Internal& state, uint64_t mhAddress, const ld:: LOH_ASSERT(!ldrInfoB.isFloat); LOH_ASSERT(ldrInfoC.baseReg == ldrInfoB.reg); //fprintf(stderr, "infoA.target=%p, %s, infoA.targetAddress=0x%08llX\n", infoA.target, infoA.target->name(), infoA.targetAddress); - targetFourByteAligned = ( ((infoA.targetAddress) & 0x3) == 0 ); - if ( usableSegment && targetFourByteAligned && withinOneMeg(infoB.instructionAddress, infoA.targetAddress) ) { + targetFourByteAligned = ( ((infoA.targetAddress + ldrInfoC.offset) & 0x3) == 0 ); + if ( usableSegment && targetFourByteAligned && withinOneMeg(infoB.instructionAddress, infoA.targetAddress + ldrInfoC.offset) ) { // can do T5 transform set32LE(infoA.instructionContent, makeNOP()); set32LE(infoB.instructionContent, makeLDR_literal(ldrInfoB, infoA.targetAddress, infoB.instructionAddress)); @@ -2264,11 +2263,11 @@ void OutputFile::applyFixUps(ld::Internal& state, uint64_t mhAddress, const ld:: LOH_ASSERT(addInfoB.destReg == ldrInfoC.baseReg); targetFourByteAligned = ( ((infoA.targetAddress) & 0x3) == 0 ); literalableSize = ( (ldrInfoC.size != 1) && (ldrInfoC.size != 2) ); - if ( usableSegment && literalableSize && targetFourByteAligned && withinOneMeg(infoC.instructionAddress, infoA.targetAddress) ) { + if ( usableSegment && literalableSize && targetFourByteAligned && withinOneMeg(infoC.instructionAddress, infoA.targetAddress + ldrInfoC.offset) ) { // can do T1 transform set32LE(infoA.instructionContent, makeNOP()); set32LE(infoB.instructionContent, makeNOP()); - set32LE(infoC.instructionContent, makeLDR_literal(ldrInfoC, infoA.targetAddress, infoC.instructionAddress)); + set32LE(infoC.instructionContent, makeLDR_literal(ldrInfoC, infoA.targetAddress + ldrInfoC.offset, infoC.instructionAddress)); if ( _options.verboseOptimizationHints() ) fprintf(stderr, "adrp-ldr-got-ldr at 0x%08llX T1 transformed to LDR literal\n", infoC.instructionAddress); } @@ -2281,14 +2280,14 @@ void OutputFile::applyFixUps(ld::Internal& state, uint64_t mhAddress, const ld:: fprintf(stderr, "adrp-ldr-got-ldr at 0x%08llX T4 transformed to ADR/LDR\n", infoC.instructionAddress); } } - else if ( (infoA.targetAddress % ldrInfoC.size) == 0 ) { + else if ( ((infoA.targetAddress % ldrInfoC.size) == 0) && ((addInfoB.addend + ldrInfoC.offset) < 4096) ) { // can do T2 transform set32LE(infoB.instructionContent, makeNOP()); ldrInfoC.baseReg = adrpInfoA.destReg; - ldrInfoC.offset = addInfoB.addend; + ldrInfoC.offset += addInfoB.addend; set32LE(infoC.instructionContent, makeLoadOrStore(ldrInfoC)); if ( _options.verboseOptimizationHints() ) { - fprintf(stderr, "adrp-ldr-got-ldr at 0x%08llX T4 transformed to ADRP/NOP/LDR\n", infoC.instructionAddress); + fprintf(stderr, "adrp-ldr-got-ldr at 0x%08llX T2 transformed to ADRP/NOP/LDR\n", infoC.instructionAddress); } } else { @@ -2354,7 +2353,6 @@ void OutputFile::applyFixUps(ld::Internal& state, uint64_t mhAddress, const ld:: LOH_ASSERT(isADRP); isSTR = (parseLoadOrStore(infoC.instruction, ldrInfoC) && ldrInfoC.isStore); LOH_ASSERT(isSTR); - LOH_ASSERT(ldrInfoC.offset == 0); isADD = parseADD(infoB.instruction, addInfoB); isLDR = parseLoadOrStore(infoB.instruction, ldrInfoB); if ( isLDR ) { @@ -2362,8 +2360,8 @@ void OutputFile::applyFixUps(ld::Internal& state, uint64_t mhAddress, const ld:: LOH_ASSERT(ldrInfoB.size == 8); LOH_ASSERT(!ldrInfoB.isFloat); LOH_ASSERT(ldrInfoC.baseReg == ldrInfoB.reg); - targetFourByteAligned = ( ((infoA.targetAddress) & 0x3) == 0 ); - if ( usableSegment && targetFourByteAligned && withinOneMeg(infoB.instructionAddress, infoA.targetAddress) ) { + targetFourByteAligned = ( ((infoA.targetAddress + ldrInfoC.offset) & 0x3) == 0 ); + if ( usableSegment && targetFourByteAligned && withinOneMeg(infoB.instructionAddress, infoA.targetAddress + ldrInfoC.offset) ) { // can do T5 transform set32LE(infoA.instructionContent, makeNOP()); set32LE(infoB.instructionContent, makeLDR_literal(ldrInfoB, infoA.targetAddress, infoB.instructionAddress)); @@ -2395,7 +2393,7 @@ void OutputFile::applyFixUps(ld::Internal& state, uint64_t mhAddress, const ld:: // can do T2 transform set32LE(infoB.instructionContent, makeNOP()); ldrInfoC.baseReg = adrpInfoA.destReg; - ldrInfoC.offset = addInfoB.addend; + ldrInfoC.offset += addInfoB.addend; set32LE(infoC.instructionContent, makeLoadOrStore(ldrInfoC)); if ( _options.verboseOptimizationHints() ) { fprintf(stderr, "adrp-ldr-got-str at 0x%08llX T4 transformed to ADRP/NOP/STR\n", infoC.instructionAddress); @@ -3600,6 +3598,10 @@ bool OutputFile::isPcRelStore(ld::Fixup::Kind kind) case ld::Fixup::kindStoreARM64GOTLoadPageOff12: case ld::Fixup::kindStoreARM64GOTLeaPage21: case ld::Fixup::kindStoreARM64GOTLeaPageOff12: + case ld::Fixup::kindStoreARM64TLVPLoadPage21: + case ld::Fixup::kindStoreARM64TLVPLoadPageOff12: + case ld::Fixup::kindStoreARM64TLVPLoadNowLeaPage21: + case ld::Fixup::kindStoreARM64TLVPLoadNowLeaPageOff12: case ld::Fixup::kindStoreARM64PCRelToGOT: case ld::Fixup::kindStoreTargetAddressARM64Page21: case ld::Fixup::kindStoreTargetAddressARM64PageOff12: @@ -3607,6 +3609,10 @@ bool OutputFile::isPcRelStore(ld::Fixup::Kind kind) case ld::Fixup::kindStoreTargetAddressARM64GOTLoadPageOff12: case ld::Fixup::kindStoreTargetAddressARM64GOTLeaPage21: case ld::Fixup::kindStoreTargetAddressARM64GOTLeaPageOff12: + case ld::Fixup::kindStoreTargetAddressARM64TLVPLoadPage21: + case ld::Fixup::kindStoreTargetAddressARM64TLVPLoadPageOff12: + case ld::Fixup::kindStoreTargetAddressARM64TLVPLoadNowLeaPage21: + case ld::Fixup::kindStoreTargetAddressARM64TLVPLoadNowLeaPageOff12: #endif return true; case ld::Fixup::kindStoreTargetAddressX86BranchPCRel32: @@ -3670,6 +3676,10 @@ bool OutputFile::setsTarget(ld::Fixup::Kind kind) case ld::Fixup::kindStoreTargetAddressARM64GOTLoadPageOff12: case ld::Fixup::kindStoreTargetAddressARM64GOTLeaPage21: case ld::Fixup::kindStoreTargetAddressARM64GOTLeaPageOff12: + case ld::Fixup::kindStoreTargetAddressARM64TLVPLoadPage21: + case ld::Fixup::kindStoreTargetAddressARM64TLVPLoadPageOff12: + case ld::Fixup::kindStoreTargetAddressARM64TLVPLoadNowLeaPage21: + case ld::Fixup::kindStoreTargetAddressARM64TLVPLoadNowLeaPageOff12: #endif return true; case ld::Fixup::kindStoreX86DtraceCallSiteNop: @@ -4529,9 +4539,12 @@ void OutputFile::makeSplitSegInfo(ld::Internal& state) case ld::Fixup::kindStoreARM64GOTLoadPage21: case ld::Fixup::kindStoreARM64GOTLeaPage21: case ld::Fixup::kindStoreARM64TLVPLoadPage21: + case ld::Fixup::kindStoreARM64TLVPLoadNowLeaPage21: case ld::Fixup::kindStoreTargetAddressARM64Page21: case ld::Fixup::kindStoreTargetAddressARM64GOTLoadPage21: case ld::Fixup::kindStoreTargetAddressARM64GOTLeaPage21: + case ld::Fixup::kindStoreTargetAddressARM64TLVPLoadPage21: + case ld::Fixup::kindStoreTargetAddressARM64TLVPLoadNowLeaPage21: case ld::Fixup::kindStoreARM64PCRelToGOT: #endif assert(target != NULL); @@ -4666,9 +4679,12 @@ void OutputFile::makeSplitSegInfoV2(ld::Internal& state) case ld::Fixup::kindStoreARM64GOTLoadPage21: case ld::Fixup::kindStoreARM64GOTLeaPage21: case ld::Fixup::kindStoreARM64TLVPLoadPage21: + case ld::Fixup::kindStoreARM64TLVPLoadNowLeaPage21: case ld::Fixup::kindStoreTargetAddressARM64Page21: case ld::Fixup::kindStoreTargetAddressARM64GOTLoadPage21: case ld::Fixup::kindStoreTargetAddressARM64GOTLeaPage21: + case ld::Fixup::kindStoreTargetAddressARM64TLVPLoadPage21: + case ld::Fixup::kindStoreTargetAddressARM64TLVPLoadNowLeaPage21: if ( fromSectionIndex != toSectionIndex ) kind = DYLD_CACHE_ADJ_V2_ARM64_ADRP; break; diff --git a/src/ld/OutputFile.h b/src/ld/OutputFile.h index cbb1cfe..5fd27d8 100644 --- a/src/ld/OutputFile.h +++ b/src/ld/OutputFile.h @@ -147,6 +147,7 @@ public: uint8_t targetSectionIndex; uint8_t referenceKind; }; + static void dumpAtomsBySection(ld::Internal& state, bool); private: void writeAtoms(ld::Internal& state, uint8_t* wholeBuffer); @@ -237,7 +238,6 @@ private: uint64_t sectionOffsetOf(const ld::Internal& state, const ld::Fixup* fixup); uint64_t tlvTemplateOffsetOf(const ld::Internal& state, const ld::Fixup* fixup); - void dumpAtomsBySection(ld::Internal& state, bool); void synthesizeDebugNotes(ld::Internal& state); const char* assureFullPath(const char* path); void noteTextReloc(const ld::Atom* atom, const ld::Atom* target); diff --git a/src/ld/Resolver.cpp b/src/ld/Resolver.cpp index 948906f..ec04b46 100644 --- a/src/ld/Resolver.cpp +++ b/src/ld/Resolver.cpp @@ -362,6 +362,11 @@ void Resolver::doFile(const ld::File& file) for (relocatable::File::LinkerOptionsList::const_iterator it=lo->begin(); it != lo->end(); ++it) { this->doLinkerOption(*it, file.path()); } + // process any additional linker-options introduced by this new archive member being loaded + if ( _completedInitialObjectFiles ) { + _inputFiles.addLinkerOptionLibraries(_internal, *this); + _inputFiles.createIndirectDylibs(); + } } // Resolve bitcode section in the object file if ( _options.bundleBitcode() ) { @@ -618,7 +623,7 @@ void Resolver::doFile(const ld::File& file) break; } if ( _options.checkDylibsAreAppExtensionSafe() && !dylibFile->appExtensionSafe() ) { - warning("linking against dylib not safe for use in application extensions: %s", file.path()); + warning("linking against a dylib which is not safe for use in application extensions: %s", file.path()); } const char* depInstallName = dylibFile->installPath(); // embedded frameworks are only supported on iOS 8 and later @@ -944,6 +949,10 @@ void Resolver::resolveUndefines() } } + // After resolving all the undefs within the linkageUnit, record all the remaining undefs and all the proxies. + if (_options.bundleBitcode() && _options.hideSymbols()) + _symbolTable.mustPreserveForBitcode(_internal.allUndefProxies); + } @@ -1705,6 +1714,7 @@ void Resolver::linkTimeOptimize() optOpt.simulator = _options.targetIOSSimulator(); optOpt.ignoreMismatchPlatform = ((_options.outputKind() == Options::kPreload) || (_options.outputKind() == Options::kStaticExecutable)); optOpt.bitcodeBundle = _options.bundleBitcode(); + optOpt.maxDefaultCommonAlignment = _options.maxDefaultCommonAlign(); optOpt.arch = _options.architecture(); optOpt.mcpu = _options.mcpuLTO(); optOpt.platform = _options.platform(); diff --git a/src/ld/SymbolTable.cpp b/src/ld/SymbolTable.cpp index 6014779..4be0c5e 100644 --- a/src/ld/SymbolTable.cpp +++ b/src/ld/SymbolTable.cpp @@ -566,6 +566,18 @@ void SymbolTable::tentativeDefs(std::vector& tents) } +void SymbolTable::mustPreserveForBitcode(std::unordered_set& syms) +{ + // return all names in _byNameTable that have no associated atom + for (const auto &entry: _byNameTable) { + const char* name = entry.first; + const ld::Atom* atom = _indirectBindingTable[entry.second]; + if ( (atom == NULL) || (atom->definition() == ld::Atom::definitionProxy) ) + syms.insert(name); + } +} + + bool SymbolTable::hasName(const char* name) { NameToSlot::iterator pos = _byNameTable.find(name); diff --git a/src/ld/SymbolTable.h b/src/ld/SymbolTable.h index ce2f1dc..2708f1f 100644 --- a/src/ld/SymbolTable.h +++ b/src/ld/SymbolTable.h @@ -120,6 +120,7 @@ public: unsigned int updateCount() { return _indirectBindingTable.size(); } void undefines(std::vector& undefines); void tentativeDefs(std::vector& undefines); + void mustPreserveForBitcode(std::unordered_set& syms); void removeDeadAtoms(); bool hasName(const char* name); bool hasExternalTentativeDefinitions() { return _hasExternalTentativeDefinitions; } diff --git a/src/ld/ld.cpp b/src/ld/ld.cpp index 0b5bc4a..a856598 100644 --- a/src/ld/ld.cpp +++ b/src/ld/ld.cpp @@ -80,6 +80,7 @@ extern "C" double log2 ( double ); #include "passes/objc.h" #include "passes/dylibs.h" #include "passes/bitcode_bundle.h" +#include "passes/code_dedup.h" #include "parsers/archive_file.h" #include "parsers/macho_relocatable_file.h" @@ -956,6 +957,7 @@ uint64_t InternalState::assignFileOffsets() lastSegName = ""; ld::Internal::FinalSection* overlappingFixedSection = NULL; ld::Internal::FinalSection* overlappingFlowSection = NULL; + ld::Internal::FinalSection* prevSect = NULL; if ( log ) fprintf(stderr, "Regular layout segments:\n"); for (std::vector::iterator it = sections.begin(); it != sections.end(); ++it) { ld::Internal::FinalSection* sect = *it; @@ -985,7 +987,16 @@ uint64_t InternalState::assignFileOffsets() // update section info sect->address = address; sect->alignmentPaddingBytes = (address - unalignedAddress); - + + // if first section is more aligned than segment, move segment start up to match + if ( (prevSect != NULL) && (prevSect->type() == ld::Section::typeFirstSection) && (strcmp(prevSect->segmentName(), sect->segmentName()) == 0) ) { + assert(prevSect->size == 0); + if ( prevSect->address != sect->address ) { + prevSect->alignmentPaddingBytes += (sect->address - prevSect->address); + prevSect->address = sect->address; + } + } + // sanity check size if ( ((address + sect->size) > _options.maxAddress()) && (_options.outputKind() != Options::kObjectFile) && (_options.outputKind() != Options::kStaticExecutable) ) @@ -1021,6 +1032,7 @@ uint64_t InternalState::assignFileOffsets() // update running totals if ( !sect->isSectionHidden() || hiddenSectionsOccupyAddressSpace ) address += sect->size; + prevSect = sect; } if ( overlappingFixedSection != NULL ) { fprintf(stderr, "Section layout:\n"); @@ -1213,7 +1225,8 @@ int main(int argc, const char* argv[]) ld::passes::dylibs::doPass(options, state); // must be after stubs and GOT passes ld::passes::order::doPass(options, state); state.markAtomsOrdered(); - ld::passes::branch_shim::doPass(options, state); // must be after stubs + ld::passes::dedup::doPass(options, state); + ld::passes::branch_shim::doPass(options, state); // must be after stubs ld::passes::branch_island::doPass(options, state); // must be after stubs and order pass ld::passes::dtrace::doPass(options, state); ld::passes::compact_unwind::doPass(options, state); // must be after order pass diff --git a/src/ld/ld.hpp b/src/ld/ld.hpp index e297036..89c091b 100644 --- a/src/ld/ld.hpp +++ b/src/ld/ld.hpp @@ -246,12 +246,13 @@ namespace dylib { }; File(const char* pth, time_t modTime, Ordinal ord) - : ld::File(pth, modTime, ord, Dylib), _dylibInstallPath(NULL), + : ld::File(pth, modTime, ord, Dylib), _dylibInstallPath(NULL), _frameworkName(NULL), _dylibTimeStamp(0), _dylibCurrentVersion(0), _dylibCompatibilityVersion(0), _explicitlyLinked(false), _implicitlyLinked(false), _lazyLoadedDylib(false), _forcedWeakLinked(false), _reExported(false), _upward(false), _dead(false) { } const char* installPath() const { return _dylibInstallPath; } + const char* frameworkName() const { return _frameworkName; } uint32_t timestamp() const { return _dylibTimeStamp; } uint32_t currentVersion() const { return _dylibCurrentVersion; } uint32_t compatibilityVersion() const{ return _dylibCompatibilityVersion; } @@ -285,13 +286,9 @@ namespace dylib { virtual bool installPathVersionSpecific() const { return false; } virtual bool appExtensionSafe() const = 0; - protected: - struct ReExportChain { ReExportChain* prev; const File* file; }; - virtual std::pair hasWeakDefinitionImpl(const char* name) const = 0; - virtual bool containsOrReExports(const char* name, bool& weakDef, bool& tlv, uint64_t& defAddress) const = 0; - virtual void assertNoReExportCycles(ReExportChain*) const = 0; - + public: const char* _dylibInstallPath; + const char* _frameworkName; uint32_t _dylibTimeStamp; uint32_t _dylibCurrentVersion; uint32_t _dylibCompatibilityVersion; @@ -446,7 +443,7 @@ struct Fixup // data-in-code markers kindDataInCodeStartData, kindDataInCodeStartJT8, kindDataInCodeStartJT16, kindDataInCodeStartJT32, kindDataInCodeStartJTA32, kindDataInCodeEnd, - // linker optimzation hints + // linker optimization hints kindLinkerOptimizationHint, // pointer store combinations kindStoreTargetAddressLittleEndian32, // kindSetTargetAddress + kindStoreLittleEndian32 @@ -895,8 +892,11 @@ public: AtomToSection atomToSection; CStringSet linkerOptionLibraries; CStringSet linkerOptionFrameworks; + CStringSet linkerOptionLibrariesProcessed; + CStringSet linkerOptionFrameworksProcessed; std::vector indirectBindingTable; std::vector filesWithBitcode; + std::unordered_set allUndefProxies; const ld::dylib::File* bundleLoader; const Atom* entryPoint; const Atom* classicBindingHelper; diff --git a/src/ld/parsers/generic_dylib_file.hpp b/src/ld/parsers/generic_dylib_file.hpp new file mode 100644 index 0000000..301074f --- /dev/null +++ b/src/ld/parsers/generic_dylib_file.hpp @@ -0,0 +1,565 @@ +/* -*- 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 __GENERIC_DYLIB_FILE_H__ +#define __GENERIC_DYLIB_FILE_H__ + +#include "ld.hpp" +#include "Options.h" +#include +#include + +namespace generic { +namespace dylib { + +// forward reference +template class File; + +// +// An ExportAtom has no content. It exists so that the linker can track which +// imported symbols came from which dynamic libraries. +// +template +class ExportAtom final : public ld::Atom +{ +public: + ExportAtom(const File& f, const char* nm, bool weakDef, bool tlv, + typename A::P::uint_t address) + : ld::Atom(f._importProxySection, ld::Atom::definitionProxy, + (weakDef ? ld::Atom::combineByName : ld::Atom::combineNever), + ld::Atom::scopeLinkageUnit, + (tlv ? ld::Atom::typeTLV : ld::Atom::typeUnclassified), + symbolTableNotIn, false, false, false, ld::Atom::Alignment(0)), + _file(f), + _name(nm), + _address(address) + {} + + // overrides of ld::Atom + virtual const ld::File* file() const override final { return &_file; } + virtual const char* name() const override final { return _name; } + virtual uint64_t size() const override final { return 0; } + virtual uint64_t objectAddress() const override final { return _address; } + virtual void copyRawContent(uint8_t buffer[]) const override final { } + + virtual void setScope(Scope) { } + +private: + using pint_t = typename A::P::uint_t; + + virtual ~ExportAtom() {} + + const File& _file; + const char* _name; + pint_t _address; +}; + + +// +// An ImportAtom has no content. It exists so that when linking a main executable flat-namespace +// the imports of all flat dylibs are checked +// +template +class ImportAtom final : public ld::Atom +{ +public: + ImportAtom(File& f, std::vector& imports); + + // overrides of ld::Atom + virtual ld::File* file() const override final { return &_file; } + virtual const char* name() const override final { return "import-atom"; } + virtual uint64_t size() const override final { return 0; } + virtual uint64_t objectAddress() const override final { return 0; } + virtual ld::Fixup::iterator fixupsBegin() const override final { return &_undefs[0]; } + virtual ld::Fixup::iterator fixupsEnd() const override final { return &_undefs[_undefs.size()]; } + virtual void copyRawContent(uint8_t buffer[]) const override final { } + + virtual void setScope(Scope) { } + +private: + virtual ~ImportAtom() {} + + File& _file; + mutable std::vector _undefs; +}; + +template +ImportAtom::ImportAtom(File& f, std::vector& imports) + : ld::Atom(f._flatDummySection, ld::Atom::definitionRegular, ld::Atom::combineNever, + ld::Atom::scopeTranslationUnit, ld::Atom::typeUnclassified, symbolTableNotIn, false, + false, false, ld::Atom::Alignment(0)), + _file(f) +{ + for(auto *name : imports) + _undefs.emplace_back(0, ld::Fixup::k1of1, ld::Fixup::kindNone, false, strdup(name)); +} + +// +// A generic representation for the dynamic library files we support (Mach-O and text-based stubs). +// Common state and functionality is consolidated in this class. +// +template +class File : public ld::dylib::File +{ +public: + File(const char* path, time_t mTime, ld::File::Ordinal ordinal, Options::Platform platform, + uint32_t linkMinOSVersion, bool linkingFlatNamespace, bool hoistImplicitPublicDylibs, + bool allowSimToMacOSX, bool addVers); + + // overrides of ld::File + virtual bool forEachAtom(ld::File::AtomHandler&) const override final; + virtual bool justInTimeforEachAtom(const char* name, ld::File::AtomHandler&) const override final; + virtual ld::File::ObjcConstraint objCConstraint() const override final { return _objcConstraint; } + virtual uint8_t swiftVersion() const override final { return _swiftVersion; } + virtual uint32_t minOSVersion() const override final { return _minVersionInDylib; } + virtual uint32_t platformLoadCommand() const override final { return _platformInDylib; } + virtual ld::Bitcode* getBitcode() const override final { return _bitcode.get(); } + + + // overrides of ld::dylib::File + virtual void processIndirectLibraries(ld::dylib::File::DylibHandler*, bool addImplicitDylibs) override final; + virtual bool providedExportAtom() const override final { return _providedAtom; } + virtual const char* parentUmbrella() const override final { return _parentUmbrella; } + virtual const std::vector* allowableClients() const override final { return _allowableClients.empty() ? nullptr : &_allowableClients; } + virtual bool hasWeakExternals() const override final { return _hasWeakExports; } + virtual bool deadStrippable() const override final { return _deadStrippable; } + virtual bool hasWeakDefinition(const char* name) const override final; + virtual bool hasPublicInstallName() const override final { return _hasPublicInstallName; } + virtual bool allSymbolsAreWeakImported() const override final; + virtual bool installPathVersionSpecific() const override final { return _installPathOverride; } + virtual bool appExtensionSafe() const override final { return _appExtensionSafe; }; + + + bool wrongOS() const { return _wrongOS; } + +private: + using pint_t = typename A::P::uint_t; + + friend class ExportAtom; + friend class ImportAtom; + + struct CStringHash { + std::size_t operator()(const char* __s) const { + unsigned long __h = 0; + for ( ; *__s; ++__s) + __h = 5 * __h + *__s; + return size_t(__h); + }; + }; + +protected: + struct AtomAndWeak { ld::Atom* atom; bool weakDef; bool tlv; pint_t address; }; + struct Dependent { + const char* path; + File* dylib; + bool reExport; + + Dependent(const char* path, bool reExport) + : path(path), dylib(nullptr), reExport(reExport) {} + }; + struct ReExportChain { ReExportChain* prev; const File* file; }; + +private: + using NameToAtomMap = std::unordered_map; + using NameSet = std::unordered_set; + + std::pair hasWeakDefinitionImpl(const char* name) const; + bool containsOrReExports(const char* name, bool& weakDef, bool& tlv, pint_t& addr) const; + void assertNoReExportCycles(ReExportChain*) const; + +protected: + bool isPublicLocation(const char* path) const; + void addSymbol(const char* name, bool weak = false, bool tlv = false, pint_t address = 0); + +private: + ld::Section _importProxySection; + ld::Section _flatDummySection; + mutable bool _providedAtom; + bool _indirectDylibsProcessed; + +protected: + mutable NameToAtomMap _atoms; + NameSet _ignoreExports; + std::vector _dependentDylibs; + ImportAtom* _importAtom; + std::vector _allowableClients; + const char* _parentUmbrella; + std::unique_ptr _bitcode; + const Options::Platform _platform; + ld::File::ObjcConstraint _objcConstraint; + const uint32_t _linkMinOSVersion; + uint32_t _minVersionInDylib; + uint32_t _platformInDylib; + uint8_t _swiftVersion; + bool _wrongOS; + bool _linkingFlat; + bool _noRexports; + bool _explictReExportFound; + bool _implicitlyLinkPublicDylibs; + bool _installPathOverride; + bool _hasWeakExports; + bool _deadStrippable; + bool _hasPublicInstallName; + bool _appExtensionSafe; + + const bool _allowSimToMacOSXLinking; + const bool _addVersionLoadCommand; + + static bool _s_logHashtable; +}; + +template +bool File::_s_logHashtable = false; + +template +File::File(const char* path, time_t mTime, ld::File::Ordinal ord, Options::Platform platform, + uint32_t linkMinOSVersion, bool linkingFlatNamespace, + bool hoistImplicitPublicDylibs, + bool allowSimToMacOSX, bool addVers) + : ld::dylib::File(path, mTime, ord), + _importProxySection("__TEXT", "__import", ld::Section::typeImportProxies, true), + _flatDummySection("__LINKEDIT", "__flat_dummy", ld::Section::typeLinkEdit, true), + _providedAtom(false), + _indirectDylibsProcessed(false), + _importAtom(nullptr), + _parentUmbrella(nullptr), + _platform(platform), + _objcConstraint(ld::File::objcConstraintNone), + _linkMinOSVersion(linkMinOSVersion), + _minVersionInDylib(0), + _platformInDylib(Options::kPlatformUnknown), + _swiftVersion(0), + _wrongOS(false), + _linkingFlat(linkingFlatNamespace), + _noRexports(false), + _explictReExportFound(false), + _implicitlyLinkPublicDylibs(hoistImplicitPublicDylibs), + _installPathOverride(false), + _hasWeakExports(false), + _deadStrippable(false), + _hasPublicInstallName(false), + _appExtensionSafe(false), + _allowSimToMacOSXLinking(allowSimToMacOSX), + _addVersionLoadCommand(addVers) +{ +} + +template +std::pair File::hasWeakDefinitionImpl(const char* name) const +{ + const auto pos = _atoms.find(name); + if ( pos != this->_atoms.end() ) + return std::make_pair(true, pos->second.weakDef); + + // look in re-exported libraries. + for (const auto &dep : _dependentDylibs) { + if ( dep.reExport ) { + auto ret = dep.dylib->hasWeakDefinitionImpl(name); + if ( ret.first ) + return ret; + } + } + return std::make_pair(false, false); +} + +template +bool File::hasWeakDefinition(const char* name) const +{ + // If we are supposed to ignore this export, then pretend we don't have it. + if ( _ignoreExports.count(name) != 0 ) + return false; + + return hasWeakDefinitionImpl(name).second; +} + +template +bool File::containsOrReExports(const char* name, bool& weakDef, bool& tlv, pint_t& addr) const +{ + if ( _ignoreExports.count(name) != 0 ) + return false; + + // check myself + const auto pos = _atoms.find(name); + if ( pos != _atoms.end() ) { + weakDef = pos->second.weakDef; + tlv = pos->second.tlv; + addr = pos->second.address; + return true; + } + + // check dylibs I re-export + for (const auto& dep : _dependentDylibs) { + if ( dep.reExport && !dep.dylib->implicitlyLinked() ) { + if ( dep.dylib->containsOrReExports(name, weakDef, tlv, addr) ) + return true; + } + } + + return false; +} + +template +bool File::forEachAtom(ld::File::AtomHandler& handler) const +{ + handler.doFile(*this); + + // if doing flatnamespace and need all this dylib's imports resolve + // add atom which references alls undefines in this dylib + if ( _importAtom != nullptr ) { + handler.doAtom(*_importAtom); + return true; + } + return false; +} + +template +bool File::justInTimeforEachAtom(const char* name, ld::File::AtomHandler& handler) const +{ + // If we are supposed to ignore this export, then pretend we don't have it. + if ( _ignoreExports.count(name) != 0 ) + return false; + + + AtomAndWeak bucket; + if ( containsOrReExports(name, bucket.weakDef, bucket.tlv, bucket.address) ) { + bucket.atom = new ExportAtom(*this, name, bucket.weakDef, bucket.tlv, bucket.address); + _atoms[name] = bucket; + _providedAtom = true; + if ( _s_logHashtable ) + fprintf(stderr, "getJustInTimeAtomsFor: %s found in %s\n", name, this->path()); + // call handler with new export atom + handler.doAtom(*bucket.atom); + return true; + } + + return false; +} + +template +void File::assertNoReExportCycles(ReExportChain* prev) const +{ + // recursively check my re-exported dylibs + ReExportChain chain = { prev, this }; + for (const auto &dep : _dependentDylibs) { + if ( dep.reExport ) { + auto* child = dep.dylib; + // check child is not already in chain + for (auto* p = prev; p != nullptr; p = p->prev) { + if ( p->file == child ) { + throwf("cycle in dylib re-exports with %s and %s", child->path(), this->path()); + } + } + if ( dep.dylib != nullptr ) + dep.dylib->assertNoReExportCycles(&chain); + } + } +} + +template +void File::processIndirectLibraries(ld::dylib::File::DylibHandler* handler, bool addImplicitDylibs) +{ + // only do this once + if ( _indirectDylibsProcessed ) + return; + + const static bool log = false; + if ( log ) + fprintf(stderr, "processIndirectLibraries(%s)\n", this->installPath()); + if ( _linkingFlat ) { + for (auto &dep : _dependentDylibs) + dep.dylib = (File*)handler->findDylib(dep.path, this->path()); + } + else if ( _noRexports ) { + // MH_NO_REEXPORTED_DYLIBS bit set, then nothing to do + } + else { + // two-level, might have re-exports + for (auto &dep : this->_dependentDylibs) { + if ( dep.reExport ) { + if ( log ) + fprintf(stderr, "processIndirectLibraries() parent=%s, child=%s\n", this->installPath(), dep.path); + // a LC_REEXPORT_DYLIB, LC_SUB_UMBRELLA or LC_SUB_LIBRARY says we re-export this child + dep.dylib = (File*)handler->findDylib(dep.path, this->path()); + if ( dep.dylib->hasPublicInstallName() && !dep.dylib->wrongOS() ) { + // promote this child to be automatically added as a direct dependent if this already is + if ( (this->explicitlyLinked() || this->implicitlyLinked()) && (strcmp(dep.path, dep.dylib->installPath()) == 0) ) { + if ( log ) + fprintf(stderr, "processIndirectLibraries() implicitly linking %s\n", dep.dylib->installPath()); + dep.dylib->setImplicitlyLinked(); + } + else if ( dep.dylib->explicitlyLinked() || dep.dylib->implicitlyLinked() ) { + if ( log ) + fprintf(stderr, "processIndirectLibraries() parent is not directly linked, but child is, so no need to re-export child\n"); + } + else { + if ( log ) + fprintf(stderr, "processIndirectLibraries() parent is not directly linked, so parent=%s will re-export child=%s\n", this->installPath(), dep.path); + } + } + else { + // add all child's symbols to me + if ( log ) + fprintf(stderr, "processIndirectLibraries() child is not public, so parent=%s will re-export child=%s\n", this->installPath(), dep.path); + } + } + else if ( !_explictReExportFound ) { + // see if child contains LC_SUB_FRAMEWORK with my name + dep.dylib = (File*)handler->findDylib(dep.path, this->path()); + const char* parentUmbrellaName = dep.dylib->parentUmbrella(); + if ( parentUmbrellaName != nullptr ) { + const char* parentName = this->path(); + const char* lastSlash = strrchr(parentName, '/'); + if ( (lastSlash != nullptr) && (strcmp(&lastSlash[1], parentUmbrellaName) == 0) ) { + // add all child's symbols to me + dep.reExport = true; + if ( log ) + fprintf(stderr, "processIndirectLibraries() umbrella=%s will re-export child=%s\n", this->installPath(), dep.path); + } + } + } + } + } + + // check for re-export cycles + ReExportChain chain = { nullptr, this }; + this->assertNoReExportCycles(&chain); + + _indirectDylibsProcessed = true; +} + +template +bool File::isPublicLocation(const char* path) const +{ + // -no_implicit_dylibs disables this optimization + if ( ! _implicitlyLinkPublicDylibs ) + return false; + + // /usr/lib is a public location + if ( (strncmp(path, "/usr/lib/", 9) == 0) && (strchr(&path[9], '/') == nullptr) ) + return true; + + // /System/Library/Frameworks/ is a public location + if ( strncmp(path, "/System/Library/Frameworks/", 27) == 0 ) { + const char* frameworkDot = strchr(&path[27], '.'); + // but only top level framework + // /System/Library/Frameworks/Foo.framework/Versions/A/Foo ==> true + // /System/Library/Frameworks/Foo.framework/Resources/libBar.dylib ==> false + // /System/Library/Frameworks/Foo.framework/Frameworks/Bar.framework/Bar ==> false + // /System/Library/Frameworks/Foo.framework/Frameworks/Xfoo.framework/XFoo ==> false + if ( frameworkDot != nullptr ) { + int frameworkNameLen = frameworkDot - &path[27]; + if ( strncmp(&path[strlen(path)-frameworkNameLen-1], &path[26], frameworkNameLen+1) == 0 ) + return true; + } + } + + return false; +} + +template +void File::addSymbol(const char* name, bool weakDef, bool tlv, pint_t address) +{ + // symbols that start with $ld$ are meta-data to the static linker + // need way for ld and dyld to see different exported symbols in a dylib + if ( strncmp(name, "$ld$", 4) == 0 ) { + // $ld$ $ $ + const char* symAction = &name[4]; + const char* symCond = strchr(symAction, '$'); + if ( symCond != nullptr ) { + char curOSVers[16]; + sprintf(curOSVers, "$os%d.%d$", (_linkMinOSVersion >> 16), ((_linkMinOSVersion >> 8) & 0xFF)); + if ( strncmp(symCond, curOSVers, strlen(curOSVers)) == 0 ) { + const char* symName = strchr(&symCond[1], '$'); + if ( symName != nullptr ) { + ++symName; + if ( strncmp(symAction, "hide$", 5) == 0 ) { + if ( _s_logHashtable ) + fprintf(stderr, " adding %s to ignore set for %s\n", symName, this->path()); + _ignoreExports.insert(strdup(symName)); + return; + } + else if ( strncmp(symAction, "add$", 4) == 0 ) { + this->addSymbol(symName, weakDef); + return; + } + else if ( strncmp(symAction, "install_name$", 13) == 0 ) { + _dylibInstallPath = strdup(symName); + _installPathOverride = true; + // CoreGraphics redirects to ApplicationServices, but with wrong compat version + if ( strcmp(_dylibInstallPath, "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices") == 0 ) + _dylibCompatibilityVersion = Options::parseVersionNumber32("1.0"); + return; + } + else if ( strncmp(symAction, "compatibility_version$", 22) == 0 ) { + _dylibCompatibilityVersion = Options::parseVersionNumber32(symName); + return; + } + else { + warning("bad symbol action: %s in dylib %s", name, this->path()); + } + } + } + } + else { + warning("bad symbol condition: %s in dylib %s", name, this->path()); + } + } + + // add symbol as possible export if we are not supposed to ignore it + if ( _ignoreExports.count(name) == 0 ) { + AtomAndWeak bucket = { nullptr, weakDef, tlv, address }; + if ( this->_s_logHashtable ) + fprintf(stderr, " adding %s to hash table for %s\n", name, this->path()); + _atoms[strdup(name)] = bucket; + } +} + +// If only weak_import symbols are used, linker should use LD_LOAD_WEAK_DYLIB +template +bool File::allSymbolsAreWeakImported() const +{ + bool foundNonWeakImport = false; + bool foundWeakImport = false; + //fprintf(stderr, "%s:\n", this->path()); + for (const auto &it : _atoms) { + auto* atom = it.second.atom; + if ( atom != nullptr ) { + if ( atom->weakImported() ) + foundWeakImport = true; + else + foundNonWeakImport = true; + //fprintf(stderr, " weak_import=%d, name=%s\n", atom->weakImported(), it->first); + } + } + + // don't automatically weak link dylib with no imports + // so at least one weak import symbol and no non-weak-imported symbols must be found + return foundWeakImport && !foundNonWeakImport; +} + + +} // end namespace dylib +} // end namespace generic + +#endif // __GENERIC_DYLIB_FILE_H__ diff --git a/src/ld/parsers/lto_file.cpp b/src/ld/parsers/lto_file.cpp index d81e833..f0f5f10 100644 --- a/src/ld/parsers/lto_file.cpp +++ b/src/ld/parsers/lto_file.cpp @@ -317,7 +317,8 @@ ld::relocatable::File* Parser::parseMachOFile(const uint8_t* p, size_t len, cons objOpts.srcKind = ld::relocatable::File::kSourceLTO; objOpts.treateBitcodeAsData = false; objOpts.usingBitcode = options.bitcodeBundle; - + objOpts.maxDefaultCommonAlignment = options.maxDefaultCommonAlignment; + // mach-o parsing is done in-memory, but need path for debug notes const char* path = "/tmp/lto.o"; time_t modTime = 0; @@ -1022,7 +1023,6 @@ bool isObjectFile(const uint8_t* fileContent, uint64_t fileLength, cpu_type_t ar return Parser::validFile(fileContent, fileLength, architecture, subarch); } - static ld::relocatable::File *parseImpl( const uint8_t *fileContent, uint64_t fileLength, const char *path, time_t modTime, ld::File::Ordinal ordinal, cpu_type_t architecture, @@ -1043,6 +1043,12 @@ ld::relocatable::File* parse(const uint8_t* fileContent, uint64_t fileLength, cpu_type_t architecture, cpu_subtype_t subarch, bool logAllFiles, bool verboseOptimizationHints) { + // do light weight check before acquiring lock + if ( fileLength < 4 ) + return NULL; + if ( (fileContent[0] != 0xDE) || (fileContent[1] != 0xC0) || (fileContent[2] != 0x17) || (fileContent[3] != 0x0B) ) + return NULL; + // Note: Once lto_module_create_in_local_context() and friends are thread safe // this lock can be removed. Mutex lock; diff --git a/src/ld/parsers/lto_file.h b/src/ld/parsers/lto_file.h index 6568232..38cfcd1 100644 --- a/src/ld/parsers/lto_file.h +++ b/src/ld/parsers/lto_file.h @@ -37,7 +37,7 @@ extern const char* archName(const uint8_t* fileContent, uint64_t fileLength); extern bool isObjectFile(const uint8_t* fileContent, uint64_t fileLength, cpu_type_t architecture, cpu_subtype_t subarch); -extern ld::relocatable::File* parse(const uint8_t* fileContent, uint64_t fileLength, +extern ld::relocatable::File* parse(const uint8_t* fileContent, uint64_t fileLength, const char* path, time_t modTime, ld::File::Ordinal ordinal, cpu_type_t architecture, cpu_subtype_t subarch, bool logAllFiles, bool verboseOptimizationHints); @@ -62,6 +62,7 @@ struct OptimizeOptions { bool simulator; bool ignoreMismatchPlatform; bool bitcodeBundle; + uint8_t maxDefaultCommonAlignment; cpu_type_t arch; const char* mcpu; Options::Platform platform; diff --git a/src/ld/parsers/macho_dylib_file.cpp b/src/ld/parsers/macho_dylib_file.cpp index 573f06c..24d69c8 100644 --- a/src/ld/parsers/macho_dylib_file.cpp +++ b/src/ld/parsers/macho_dylib_file.cpp @@ -34,113 +34,30 @@ #include #include #include -#include -#include #include "Architectures.hpp" #include "Bitcode.hpp" #include "MachOFileAbstraction.hpp" #include "MachOTrie.hpp" +#include "generic_dylib_file.hpp" #include "macho_dylib_file.h" #include "../code-sign-blobs/superblob.h" namespace mach_o { namespace dylib { - -// forward reference -template class File; - - -// -// An ExportAtom has no content. It exists so that the linker can track which imported -// symbols came from which dynamic libraries. -// -template -class ExportAtom : public ld::Atom -{ -public: - ExportAtom(const File& f, const char* nm, bool weakDef, - bool tlv, typename A::P::uint_t address) - : ld::Atom(f._importProxySection, ld::Atom::definitionProxy, - (weakDef? ld::Atom::combineByName : ld::Atom::combineNever), - ld::Atom::scopeLinkageUnit, - (tlv ? ld::Atom::typeTLV : ld::Atom::typeUnclassified), - symbolTableNotIn, false, false, false, ld::Atom::Alignment(0)), - _file(f), _name(nm), _address(address) {} - // overrides of ld::Atom - virtual const ld::File* file() const { return &_file; } - virtual const char* name() const { return _name; } - virtual uint64_t size() const { return 0; } - virtual uint64_t objectAddress() const { return _address; } - virtual void copyRawContent(uint8_t buffer[]) const { } - virtual void setScope(Scope) { } - -protected: - typedef typename A::P P; - typedef typename A::P::uint_t pint_t; - - virtual ~ExportAtom() {} - - const File& _file; - const char* _name; - pint_t _address; -}; - - - -// -// An ImportAtom has no content. It exists so that when linking a main executable flat-namespace -// the imports of all flat dylibs are checked -// -template -class ImportAtom : public ld::Atom -{ -public: - ImportAtom(File& f, std::vector& imports); - - // overrides of ld::Atom - virtual ld::File* file() const { return &_file; } - virtual const char* name() const { return "import-atom"; } - virtual uint64_t size() const { return 0; } - virtual uint64_t objectAddress() const { return 0; } - virtual void copyRawContent(uint8_t buffer[]) const { } - virtual void setScope(Scope) { } - virtual ld::Fixup::iterator fixupsBegin() const { return &_undefs[0]; } - virtual ld::Fixup::iterator fixupsEnd() const { return &_undefs[_undefs.size()]; } - -protected: - typedef typename A::P P; - - virtual ~ImportAtom() {} - - - File& _file; - mutable std::vector _undefs; -}; - -template -ImportAtom::ImportAtom(File& f, std::vector& imports) -: ld::Atom(f._flatDummySection, ld::Atom::definitionRegular, ld::Atom::combineNever, ld::Atom::scopeTranslationUnit, - ld::Atom::typeUnclassified, symbolTableNotIn, false, false, false, ld::Atom::Alignment(0)), _file(f) -{ - for(std::vector::iterator it=imports.begin(); it != imports.end(); ++it) { - _undefs.push_back(ld::Fixup(0, ld::Fixup::k1of1, ld::Fixup::kindNone, false, strdup(*it))); - } -} - - - // // The reader for a dylib extracts all exported symbols names from the memory-mapped // dylib, builds a hash table, then unmaps the file. This is an important memory // savings for large dylibs. // template -class File : public ld::dylib::File +class File final : public generic::dylib::File { + using Base = generic::dylib::File; + public: - static bool validFile(const uint8_t* fileContent, bool executableOrDylib); + static bool validFile(const uint8_t* fileContent, bool executableOrDylib, bool subTypeMustMatch=false); File(const uint8_t* fileContent, uint64_t fileLength, const char* path, time_t mTime, ld::File::Ordinal ordinal, bool linkingFlatNamespace, bool linkingMainExecutable, bool hoistImplicitPublicDylibs, @@ -148,106 +65,26 @@ public: bool addVers, bool buildingForSimulator, bool logAllFiles, const char* installPath, bool indirectDylib, bool ignoreMismatchPlatform, bool usingBitcode); - virtual ~File() {} - - // overrides of ld::File - virtual bool forEachAtom(ld::File::AtomHandler&) const; - virtual bool justInTimeforEachAtom(const char* name, ld::File::AtomHandler&) const; - virtual ld::File::ObjcConstraint objCConstraint() const { return _objcContraint; } - virtual uint8_t swiftVersion() const { return _swiftVersion; } - virtual uint32_t minOSVersion() const { return _minVersionInDylib; } - virtual uint32_t platformLoadCommand() const { return _platformInDylib; } - - // overrides of ld::dylib::File - virtual void processIndirectLibraries(ld::dylib::File::DylibHandler*, bool); - virtual bool providedExportAtom() const { return _providedAtom; } - virtual const char* parentUmbrella() const { return _parentUmbrella; } - virtual const std::vector* allowableClients() const { return _allowableClients.size() != 0 ? &_allowableClients : NULL; } - virtual bool hasWeakExternals() const { return _hasWeakExports; } - virtual bool deadStrippable() const { return _deadStrippable; } - virtual bool hasPublicInstallName() const{ return _hasPublicInstallName; } - virtual bool hasWeakDefinition(const char* name) const; - virtual bool allSymbolsAreWeakImported() const; - virtual bool installPathVersionSpecific() const { return _installPathOverride; } - virtual bool appExtensionSafe() const { return _appExtensionSafe; }; - virtual ld::Bitcode* getBitcode() const { return _bitcode.get(); } - -protected: - virtual void assertNoReExportCycles(ReExportChain*) const; + virtual ~File() noexcept {} private: - typedef typename A::P P; - typedef typename A::P::E E; - typedef typename A::P::uint_t pint_t; - - friend class ExportAtom; - friend class ImportAtom; - - struct CStringHash { - std::size_t operator()(const char* __s) const { - unsigned long __h = 0; - for ( ; *__s; ++__s) - __h = 5 * __h + *__s; - return size_t(__h); - }; - }; - struct AtomAndWeak { ld::Atom* atom; bool weakDef; bool tlv; uint64_t address; }; - typedef std::unordered_map NameToAtomMap; - typedef std::unordered_set NameSet; + using P = typename A::P; + using E = typename A::P::E; - struct Dependent { const char* path; File* dylib; bool reExport; }; - - virtual std::pair hasWeakDefinitionImpl(const char* name) const; - virtual bool containsOrReExports(const char* name, bool& weakDef, bool& tlv, uint64_t& defAddress) const; - bool isPublicLocation(const char* pth); - bool wrongOS() { return _wrongOS; } - void addSymbol(const char* name, bool weak, bool tlv, pint_t address); - void addDyldFastStub(); - void buildExportHashTableFromExportInfo(const macho_dyld_info_command

* dyldInfo, + void addDyldFastStub(); + void buildExportHashTableFromExportInfo(const macho_dyld_info_command

* dyldInfo, const uint8_t* fileContent); - void buildExportHashTableFromSymbolTable(const macho_dysymtab_command

* dynamicInfo, + void buildExportHashTableFromSymbolTable(const macho_dysymtab_command

* dynamicInfo, const macho_nlist

* symbolTable, const char* strings, const uint8_t* fileContent); - static uint32_t parseVersionNumber32(const char* versionString); - static const char* objCInfoSegmentName(); - static const char* objCInfoSectionName(); + static const char* objCInfoSegmentName(); + static const char* objCInfoSectionName(); - const Options::Platform _platform; - const uint32_t _linkMinOSVersion; - const bool _allowSimToMacOSXLinking; - const bool _addVersionLoadCommand; - bool _linkingFlat; - bool _implicitlyLinkPublicDylibs; - ld::File::ObjcConstraint _objcContraint; - uint8_t _swiftVersion; - ld::Section _importProxySection; - ld::Section _flatDummySection; - std::vector _dependentDylibs; - std::vector _allowableClients; - mutable NameToAtomMap _atoms; - NameSet _ignoreExports; - const char* _parentUmbrella; - ImportAtom* _importAtom; - bool _noRexports; - bool _hasWeakExports; - bool _deadStrippable; - bool _hasPublicInstallName; - mutable bool _providedAtom; - bool _explictReExportFound; - bool _wrongOS; - bool _installPathOverride; - bool _indirectDylibsProcessed; - bool _appExtensionSafe; - bool _usingBitcode; - uint32_t _minVersionInDylib; - uint32_t _platformInDylib; - std::unique_ptr _bitcode; - - static bool _s_logHashtable; -}; -template -bool File::_s_logHashtable = false; + uint64_t _fileLength; + uint32_t _linkeditStartOffset; + +}; template <> const char* File::objCInfoSegmentName() { return "__DATA"; } template <> const char* File::objCInfoSegmentName() { return "__DATA"; } @@ -258,22 +95,13 @@ template <> const char* File::objCInfoSectionName() { return "__objc_imagei template const char* File::objCInfoSectionName() { return "__image_info"; } template -File::File(const uint8_t* fileContent, uint64_t fileLength, const char* pth, time_t mTime, ld::File::Ordinal ord, - bool linkingFlatNamespace, bool linkingMainExecutable, bool hoistImplicitPublicDylibs, - Options::Platform platform, uint32_t linkMinOSVersion, bool allowSimToMacOSX, bool addVers, bool buildingForSimulator, - bool logAllFiles, const char* targetInstallPath, bool indirectDylib, bool ignoreMismatchPlatform, bool usingBitcode) - : ld::dylib::File(strdup(pth), mTime, ord), - _platform(platform), _linkMinOSVersion(linkMinOSVersion), _allowSimToMacOSXLinking(allowSimToMacOSX), _addVersionLoadCommand(addVers), - _linkingFlat(linkingFlatNamespace), _implicitlyLinkPublicDylibs(hoistImplicitPublicDylibs), - _objcContraint(ld::File::objcConstraintNone), _swiftVersion(0), - _importProxySection("__TEXT", "__import", ld::Section::typeImportProxies, true), - _flatDummySection("__LINKEDIT", "__flat_dummy", ld::Section::typeLinkEdit, true), - _parentUmbrella(NULL), _importAtom(NULL), - _noRexports(false), _hasWeakExports(false), - _deadStrippable(false), _hasPublicInstallName(false), - _providedAtom(false), _explictReExportFound(false), _wrongOS(false), _installPathOverride(false), - _indirectDylibsProcessed(false), _appExtensionSafe(false), _usingBitcode(usingBitcode), - _minVersionInDylib(0), _platformInDylib(Options::kPlatformUnknown) +File::File(const uint8_t* fileContent, uint64_t fileLength, const char* path, time_t mTime, + ld::File::Ordinal ord, bool linkingFlatNamespace, bool linkingMainExecutable, + bool hoistImplicitPublicDylibs, Options::Platform platform, uint32_t linkMinOSVersion, + bool allowSimToMacOSX, bool addVers, bool buildingForSimulator, bool logAllFiles, + const char* targetInstallPath, bool indirectDylib, bool ignoreMismatchPlatform, bool usingBitcode) + : Base(strdup(path), mTime, ord, platform, linkMinOSVersion, linkingFlatNamespace, + hoistImplicitPublicDylibs, allowSimToMacOSX, addVers), _fileLength(fileLength), _linkeditStartOffset(0) { const macho_header

* header = (const macho_header

*)fileContent; const uint32_t cmd_count = header->ncmds(); @@ -282,7 +110,7 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* pth, // write out path for -t option if ( logAllFiles ) - printf("%s\n", pth); + printf("%s\n", path); // a "blank" stub has zero load commands if ( (header->filetype() == MH_DYLIB_STUB) && (cmd_count == 0) ) { @@ -293,32 +121,33 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* pth, // optimize the case where we know there is no reason to look at indirect dylibs - _noRexports = (header->flags() & MH_NO_REEXPORTED_DYLIBS) - || (header->filetype() == MH_BUNDLE) - || (header->filetype() == MH_EXECUTE); // bundles and exectuables can be used via -bundle_loader - _hasWeakExports = (header->flags() & MH_WEAK_DEFINES); - _deadStrippable = (header->flags() & MH_DEAD_STRIPPABLE_DYLIB); - _appExtensionSafe = (header->flags() & MH_APP_EXTENSION_SAFE); + this->_noRexports = (header->flags() & MH_NO_REEXPORTED_DYLIBS) + || (header->filetype() == MH_BUNDLE) + || (header->filetype() == MH_EXECUTE); // bundles and exectuables can be used via -bundle_loader + this->_hasWeakExports = (header->flags() & MH_WEAK_DEFINES); + this->_deadStrippable = (header->flags() & MH_DEAD_STRIPPABLE_DYLIB); + this->_appExtensionSafe = (header->flags() & MH_APP_EXTENSION_SAFE); // pass 1: get pointers, and see if this dylib uses compressed LINKEDIT format - const macho_dysymtab_command

* dynamicInfo = NULL; - const macho_dyld_info_command

* dyldInfo = NULL; - const macho_nlist

* symbolTable = NULL; - const char* strings = NULL; + const macho_dysymtab_command

* dynamicInfo = nullptr; + const macho_dyld_info_command

* dyldInfo = nullptr; + const macho_nlist

* symbolTable = nullptr; + const macho_symtab_command

* symtab = nullptr; + const char* strings = nullptr; bool compressedLinkEdit = false; uint32_t dependentLibCount = 0; Options::Platform lcPlatform = Options::kPlatformUnknown; const macho_load_command

* cmd = cmds; for (uint32_t i = 0; i < cmd_count; ++i) { macho_dylib_command

* dylibID; - const macho_symtab_command

* symtab; + uint32_t cmdLength = cmd->cmdsize(); switch (cmd->cmd()) { case LC_SYMTAB: symtab = (macho_symtab_command

*)cmd; symbolTable = (const macho_nlist

*)((char*)header + symtab->symoff()); strings = (char*)header + symtab->stroff(); if ( (symtab->stroff() + symtab->strsize()) > fileLength ) - throwf("mach-o string pool extends beyond end of file in %s", pth); + throwf("mach-o string pool extends beyond end of file in %s", path); break; case LC_DYSYMTAB: dynamicInfo = (macho_dysymtab_command

*)cmd; @@ -330,27 +159,31 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* pth, break; case LC_ID_DYLIB: dylibID = (macho_dylib_command

*)cmd; - _dylibInstallPath = strdup(dylibID->name()); - _dylibTimeStamp = dylibID->timestamp(); - _dylibCurrentVersion = dylibID->current_version(); - _dylibCompatibilityVersion = dylibID->compatibility_version(); - _hasPublicInstallName = isPublicLocation(_dylibInstallPath); + if ( dylibID->name_offset() > cmdLength ) + throwf("malformed mach-o: LC_ID_DYLIB load command has offset (%u) outside its size (%u)", dylibID->name_offset(), cmdLength); + if ( (dylibID->name_offset() + strlen(dylibID->name()) + 1) > cmdLength ) + throwf("malformed mach-o: LC_ID_DYLIB load command string extends beyond end of load command"); + this->_dylibInstallPath = strdup(dylibID->name()); + this->_dylibTimeStamp = dylibID->timestamp(); + this->_dylibCurrentVersion = dylibID->current_version(); + this->_dylibCompatibilityVersion = dylibID->compatibility_version(); + this->_hasPublicInstallName = this->isPublicLocation(this->_dylibInstallPath); break; case LC_LOAD_DYLIB: case LC_LOAD_WEAK_DYLIB: ++dependentLibCount; break; case LC_REEXPORT_DYLIB: - _explictReExportFound = true; + this->_explictReExportFound = true; ++dependentLibCount; break; case LC_SUB_FRAMEWORK: - _parentUmbrella = strdup(((macho_sub_framework_command

*)cmd)->umbrella()); + this->_parentUmbrella = strdup(((macho_sub_framework_command

*)cmd)->umbrella()); break; case LC_SUB_CLIENT: - _allowableClients.push_back(strdup(((macho_sub_client_command

*)cmd)->client())); + this->_allowableClients.push_back(strdup(((macho_sub_client_command

*)cmd)->client())); // Don't hoist "public" (in /usr/lib/) dylibs that should not be directly linked - _hasPublicInstallName = false; + this->_hasPublicInstallName = false; break; case LC_VERSION_MIN_MACOSX: case LC_VERSION_MIN_IPHONEOS: @@ -358,9 +191,9 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* pth, #if SUPPORT_APPLE_TV case LC_VERSION_MIN_TVOS: #endif - _minVersionInDylib = (ld::MacVersionMin)((macho_version_min_command

*)cmd)->version(); - _platformInDylib = cmd->cmd(); - lcPlatform = Options::platformForLoadCommand(_platformInDylib); + this->_minVersionInDylib = (ld::MacVersionMin)((macho_version_min_command

*)cmd)->version(); + this->_platformInDylib = cmd->cmd(); + lcPlatform = Options::platformForLoadCommand(this->_platformInDylib); break; case LC_CODE_SIGNATURE: break; @@ -384,17 +217,17 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* pth, if ( (sect->size() >= 8) && (contents[0] == 0) ) { uint32_t flags = E::get32(contents[1]); if ( (flags & 4) == 4 ) - _objcContraint = ld::File::objcConstraintGC; + this->_objcConstraint = ld::File::objcConstraintGC; else if ( (flags & 2) == 2 ) - _objcContraint = ld::File::objcConstraintRetainReleaseOrGC; + this->_objcConstraint = ld::File::objcConstraintRetainReleaseOrGC; else if ( (flags & 32) == 32 ) - _objcContraint = ld::File::objcConstraintRetainReleaseForSimulator; + this->_objcConstraint = ld::File::objcConstraintRetainReleaseForSimulator; else - _objcContraint = ld::File::objcConstraintRetainRelease; - _swiftVersion = ((flags >> 8) & 0xFF); + this->_objcConstraint = ld::File::objcConstraintRetainRelease; + this->_swiftVersion = ((flags >> 8) & 0xFF); } else if ( sect->size() > 0 ) { - warning("can't parse %s/%s section in %s", objCInfoSegmentName(), objCInfoSectionName(), this->path()); + warning("can't parse %s/%s section in %s", objCInfoSegmentName(), objCInfoSectionName(), path); } } } @@ -404,12 +237,15 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* pth, else if ( strcmp(((macho_segment_command

*)cmd)->segname(), "__LLVM") == 0 ) { const macho_section

* const sect = (macho_section

*)((char*)cmd + sizeof(macho_segment_command

)); if ( strncmp(sect->sectname(), "__bundle", 8) == 0 ) - _bitcode = std::unique_ptr(new ld::Bitcode(NULL, sect->size())); + this->_bitcode = std::unique_ptr(new ld::Bitcode(NULL, sect->size())); + } + else if ( strcmp(((macho_segment_command

*)cmd)->segname(), "__LINKEDIT") == 0 ) { + _linkeditStartOffset = ((macho_segment_command

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

*)(((char*)cmd)+cmd->cmdsize()); + cmd = (const macho_load_command

*)(((char*)cmd)+cmdLength); if ( cmd > cmdsEnd ) - throwf("malformed dylb, load command #%d is outside size of load commands in %s", i, pth); + throwf("malformed dylb, load command #%d is outside size of load commands in %s", i, path); } // arm/arm64 objects are default to ios platform if not set. // rdar://problem/21746314 @@ -419,10 +255,10 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* pth, // check cross-linking if ( lcPlatform != platform ) { - _wrongOS = true; - if ( _addVersionLoadCommand && !indirectDylib && !ignoreMismatchPlatform ) { + this->_wrongOS = true; + if ( this->_addVersionLoadCommand && !indirectDylib && !ignoreMismatchPlatform ) { if ( buildingForSimulator ) { - if ( !_allowSimToMacOSXLinking ) { + if ( !this->_allowSimToMacOSXLinking ) { switch (platform) { case Options::kPlatformOSX: case Options::kPlatformiOS: @@ -430,10 +266,16 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* pth, break; // fall through if the Platform is not Unknown case Options::kPlatformWatchOS: - // WatchOS errors on cross-linking all the time. - throwf("building for %s simulator, but linking against dylib built for %s,", + // WatchOS errors on cross-linking when building for bitcode + if ( usingBitcode ) + throwf("building for %s simulator, but linking against dylib built for %s,", Options::platformName(platform), Options::platformName(lcPlatform)); + else + warning("URGENT: building for %s simulator, but linking against dylib (%s) built for %s. " + "Note: This will be an error in the future.", + Options::platformName(platform), path, + Options::platformName(lcPlatform)); break; #if SUPPORT_APPLE_TV case Options::kPlatform_tvOS: @@ -445,7 +287,7 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* pth, else warning("URGENT: building for %s simulator, but linking against dylib (%s) built for %s. " "Note: This will be an error in the future.", - Options::platformName(platform), path(), + Options::platformName(platform), path, Options::platformName(lcPlatform)); break; #endif @@ -463,22 +305,28 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* pth, break; // fall through if the Platform is not Unknown case Options::kPlatformWatchOS: - // WatchOS errors on cross-linking all the time. - throwf("building for %s, but linking against dylib built for %s,", + // WatchOS errors on cross-linking when building for bitcode + if ( usingBitcode ) + throwf("building for %s, but linking against dylib built for %s,", Options::platformName(platform), Options::platformName(lcPlatform)); + else + warning("URGENT: building for %s, but linking against dylib (%s) built for %s. " + "Note: This will be an error in the future.", + Options::platformName(platform), path, + Options::platformName(lcPlatform)); break; #if SUPPORT_APPLE_TV case Options::kPlatform_tvOS: // tvOS is a warning temporarily. rdar://problem/21746965 - if ( _usingBitcode ) + if ( usingBitcode ) throwf("building for %s, but linking against dylib built for %s,", Options::platformName(platform), Options::platformName(lcPlatform)); else warning("URGENT: building for %s, but linking against dylib (%s) built for %s. " "Note: This will be an error in the future.", - Options::platformName(platform), path(), + Options::platformName(platform), path, Options::platformName(lcPlatform)); break; #endif @@ -493,15 +341,17 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* pth, // figure out if we need to examine dependent dylibs // with compressed LINKEDIT format, MH_NO_REEXPORTED_DYLIBS can be trusted bool processDependentLibraries = true; - if ( compressedLinkEdit && _noRexports && !linkingFlatNamespace) + if ( compressedLinkEdit && this->_noRexports && !linkingFlatNamespace) processDependentLibraries = false; if ( processDependentLibraries ) { // pass 2 builds list of all dependent libraries - _dependentDylibs.reserve(dependentLibCount); + this->_dependentDylibs.reserve(dependentLibCount); cmd = cmds; unsigned int reExportDylibCount = 0; for (uint32_t i = 0; i < cmd_count; ++i) { + uint32_t cmdLength = cmd->cmdsize(); + const macho_dylib_command

* dylibCmd = (macho_dylib_command

*)cmd; switch (cmd->cmd()) { case LC_LOAD_DYLIB: case LC_LOAD_WEAK_DYLIB: @@ -510,20 +360,22 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* pth, break; case LC_REEXPORT_DYLIB: ++reExportDylibCount; - Dependent entry; - entry.path = strdup(((macho_dylib_command

*)cmd)->name()); - entry.dylib = NULL; - entry.reExport = (cmd->cmd() == LC_REEXPORT_DYLIB); - if ( (targetInstallPath == NULL) || (strcmp(targetInstallPath, entry.path) != 0) ) - _dependentDylibs.push_back(entry); + if ( dylibCmd->name_offset() > cmdLength ) + throwf("malformed mach-o: LC_*_DYLIB load command has offset (%u) outside its size (%u)", dylibCmd->name_offset(), cmdLength); + if ( (dylibCmd->name_offset() + strlen(dylibCmd->name()) + 1) > cmdLength ) + throwf("malformed mach-o: LC_*_DYLIB load command string extends beyond end of load command"); + const char *path = strdup(dylibCmd->name()); + bool reExport = (cmd->cmd() == LC_REEXPORT_DYLIB); + if ( (targetInstallPath == nullptr) || (strcmp(targetInstallPath, path) != 0) ) + this->_dependentDylibs.emplace_back(path, reExport); break; } - cmd = (const macho_load_command

*)(((char*)cmd)+cmd->cmdsize()); + cmd = (const macho_load_command

*)(((char*)cmd)+cmdLength); } // verify MH_NO_REEXPORTED_DYLIBS bit was correct if ( compressedLinkEdit && !linkingFlatNamespace ) { if ( reExportDylibCount == 0 ) - throwf("malformed dylib has MH_NO_REEXPORTED_DYLIBS flag but no LC_REEXPORT_DYLIB load commands: %s", pth); + throwf("malformed dylib has MH_NO_REEXPORTED_DYLIBS flag but no LC_REEXPORT_DYLIB load commands: %s", path); } // pass 3 add re-export info cmd = cmds; @@ -533,44 +385,65 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* pth, switch (cmd->cmd()) { case LC_SUB_UMBRELLA: frameworkLeafName = ((macho_sub_umbrella_command

*)cmd)->sub_umbrella(); - for (typename std::vector::iterator it = _dependentDylibs.begin(); it != _dependentDylibs.end(); ++it) { - const char* dylibName = it->path; + for (auto &dep : this->_dependentDylibs) { + const char* dylibName = dep.path; const char* lastSlash = strrchr(dylibName, '/'); - if ( (lastSlash != NULL) && (strcmp(&lastSlash[1], frameworkLeafName) == 0) ) - it->reExport = true; + if ( (lastSlash != nullptr) && (strcmp(&lastSlash[1], frameworkLeafName) == 0) ) + dep.reExport = true; } break; case LC_SUB_LIBRARY: dylibBaseName = ((macho_sub_library_command

*)cmd)->sub_library(); - for (typename std::vector::iterator it = _dependentDylibs.begin(); it != _dependentDylibs.end(); ++it) { - const char* dylibName = it->path; + for (auto &dep : this->_dependentDylibs) { + const char* dylibName = dep.path; const char* lastSlash = strrchr(dylibName, '/'); const char* leafStart = &lastSlash[1]; - if ( lastSlash == NULL ) + if ( lastSlash == nullptr ) leafStart = dylibName; const char* firstDot = strchr(leafStart, '.'); int len = strlen(leafStart); - if ( firstDot != NULL ) + if ( firstDot != nullptr ) len = firstDot - leafStart; if ( strncmp(leafStart, dylibBaseName, len) == 0 ) - it->reExport = true; + dep.reExport = true; } break; } cmd = (const macho_load_command

*)(((char*)cmd)+cmd->cmdsize()); } } - + + // if framework, capture framework name + if ( this->_dylibInstallPath != NULL ) { + const char* lastSlash = strrchr(this->_dylibInstallPath, '/'); + if ( lastSlash != NULL ) { + const char* leafName = lastSlash+1; + char frname[strlen(leafName)+32]; + strcpy(frname, leafName); + strcat(frname, ".framework/"); + + if ( strstr(this->_dylibInstallPath, frname) != NULL ) + this->_frameworkName = leafName; + } + } + // validate minimal load commands - if ( (_dylibInstallPath == NULL) && ((header->filetype() == MH_DYLIB) || (header->filetype() == MH_DYLIB_STUB)) ) - throwf("dylib %s missing LC_ID_DYLIB load command", pth); - if ( dyldInfo == NULL ) { - if ( symbolTable == NULL ) + if ( (this->_dylibInstallPath == nullptr) && ((header->filetype() == MH_DYLIB) || (header->filetype() == MH_DYLIB_STUB)) ) + throwf("dylib %s missing LC_ID_DYLIB load command", path); + if ( dyldInfo == nullptr ) { + if ( symbolTable == nullptr ) throw "binary missing LC_SYMTAB load command"; - if ( dynamicInfo == NULL ) + if ( dynamicInfo == nullptr ) throw "binary missing LC_DYSYMTAB load command"; } - + + if ( symtab != nullptr ) { + if ( symtab->symoff() < _linkeditStartOffset ) + throwf("malformed mach-o, symbol table not in __LINKEDIT"); + if ( symtab->stroff() < _linkeditStartOffset ) + throwf("malformed mach-o, symbol table strings not in __LINKEDIT"); + } + // if linking flat and this is a flat dylib, create one atom that references all imported symbols if ( linkingFlatNamespace && linkingMainExecutable && ((header->flags() & MH_TWOLEVEL) == 0) ) { std::vector importNames; @@ -580,11 +453,11 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* pth, for (const macho_nlist

* sym=start; sym < end; ++sym) { importNames.push_back(&strings[sym->n_strx()]); } - _importAtom = new ImportAtom(*this, importNames); + this->_importAtom = new generic::dylib::ImportAtom(*this, importNames); } // build hash table - if ( dyldInfo != NULL ) + if ( dyldInfo != nullptr ) buildExportHashTableFromExportInfo(dyldInfo, fileContent); else buildExportHashTableFromSymbolTable(dynamicInfo, symbolTable, strings, fileContent); @@ -593,49 +466,27 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* pth, munmap((caddr_t)fileContent, fileLength); } - -// -// Parses number of form X[.Y[.Z]] into a uint32_t where the nibbles are xxxx.yy.zz -// -template -uint32_t File::parseVersionNumber32(const char* versionString) -{ - uint32_t x = 0; - uint32_t y = 0; - uint32_t z = 0; - char* end; - x = strtoul(versionString, &end, 10); - if ( *end == '.' ) { - y = strtoul(&end[1], &end, 10); - if ( *end == '.' ) { - z = strtoul(&end[1], &end, 10); - } - } - if ( (*end != '\0') || (x > 0xffff) || (y > 0xff) || (z > 0xff) ) - throwf("malformed 32-bit x.y.z version number: %s", versionString); - - return (x << 16) | ( y << 8 ) | z; -} - template -void File::buildExportHashTableFromSymbolTable(const macho_dysymtab_command

* dynamicInfo, - const macho_nlist

* symbolTable, const char* strings, - const uint8_t* fileContent) +void File::buildExportHashTableFromSymbolTable(const macho_dysymtab_command

* dynamicInfo, + const macho_nlist

* symbolTable, + const char* strings, const uint8_t* fileContent) { if ( dynamicInfo->tocoff() == 0 ) { - if ( _s_logHashtable ) fprintf(stderr, "ld: building hashtable of %u toc entries for %s\n", dynamicInfo->nextdefsym(), this->path()); + if ( this->_s_logHashtable ) + fprintf(stderr, "ld: building hashtable of %u toc entries for %s\n", dynamicInfo->nextdefsym(), this->path()); const macho_nlist

* start = &symbolTable[dynamicInfo->iextdefsym()]; const macho_nlist

* end = &start[dynamicInfo->nextdefsym()]; - _atoms.reserve(dynamicInfo->nextdefsym()); // set initial bucket count + this->_atoms.reserve(dynamicInfo->nextdefsym()); // set initial bucket count for (const macho_nlist

* sym=start; sym < end; ++sym) { this->addSymbol(&strings[sym->n_strx()], (sym->n_desc() & N_WEAK_DEF) != 0, false, sym->n_value()); } } else { int32_t count = dynamicInfo->ntoc(); - _atoms.reserve(count); // set initial bucket count - if ( _s_logHashtable ) fprintf(stderr, "ld: building hashtable of %u entries for %s\n", count, this->path()); - const struct dylib_table_of_contents* toc = (dylib_table_of_contents*)(fileContent + dynamicInfo->tocoff()); + this->_atoms.reserve(count); // set initial bucket count + if ( this->_s_logHashtable ) + fprintf(stderr, "ld: building hashtable of %u entries for %s\n", count, this->path()); + const auto* toc = reinterpret_cast(fileContent + dynamicInfo->tocoff()); for (int32_t i = 0; i < count; ++i) { const uint32_t index = E::get32(toc[i].symbol_index); const macho_nlist

* sym = &symbolTable[index]; @@ -644,26 +495,29 @@ void File::buildExportHashTableFromSymbolTable(const macho_dysymtab_command

_dylibInstallPath != nullptr) && (strcmp(this->_dylibInstallPath, "/usr/lib/libSystem.B.dylib") == 0) ) addDyldFastStub(); } template -void File::buildExportHashTableFromExportInfo(const macho_dyld_info_command

* dyldInfo, - const uint8_t* fileContent) +void File::buildExportHashTableFromExportInfo(const macho_dyld_info_command

* dyldInfo, + const uint8_t* fileContent) { - if ( _s_logHashtable ) fprintf(stderr, "ld: building hashtable from export info in %s\n", this->path()); + if ( this->_s_logHashtable ) + fprintf(stderr, "ld: building hashtable from export info in %s\n", this->path()); if ( dyldInfo->export_size() > 0 ) { const uint8_t* start = fileContent + dyldInfo->export_off(); const uint8_t* end = &start[dyldInfo->export_size()]; + if ( (dyldInfo->export_off() + dyldInfo->export_size()) > _fileLength ) + throwf("malformed mach-o dylib, exports trie extends beyond end of file, "); std::vector list; parseTrie(start, end, list); - for (std::vector::iterator it=list.begin(); it != list.end(); ++it) - this->addSymbol(it->name, - it->flags & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION, - (it->flags & EXPORT_SYMBOL_FLAGS_KIND_MASK) == EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL, - it->address); + for (const auto &entry : list) + this->addSymbol(entry.name, + entry.flags & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION, + (entry.flags & EXPORT_SYMBOL_FLAGS_KIND_MASK) == EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL, + entry.address); } } @@ -671,13 +525,13 @@ void File::buildExportHashTableFromExportInfo(const macho_dyld_info_command

void File::addDyldFastStub() { - addSymbol("dyld_stub_binder", false, false, 0); + addSymbol("dyld_stub_binder"); } template <> void File::addDyldFastStub() { - addSymbol("dyld_stub_binder", false, false, 0); + addSymbol("dyld_stub_binder"); } template @@ -686,341 +540,34 @@ void File::addDyldFastStub() // do nothing } -template -void File::addSymbol(const char* name, bool weakDef, bool tlv, pint_t address) -{ - //fprintf(stderr, "addSymbol() %s\n", name); - // symbols that start with $ld$ are meta-data to the static linker - // need way for ld and dyld to see different exported symbols in a dylib - if ( strncmp(name, "$ld$", 4) == 0 ) { - // $ld$ $ $ - const char* symAction = &name[4]; - const char* symCond = strchr(symAction, '$'); - if ( symCond != NULL ) { - char curOSVers[16]; - sprintf(curOSVers, "$os%d.%d$", (_linkMinOSVersion >> 16), ((_linkMinOSVersion >> 8) & 0xFF)); - if ( strncmp(symCond, curOSVers, strlen(curOSVers)) == 0 ) { - const char* symName = strchr(&symCond[1], '$'); - if ( symName != NULL ) { - ++symName; - if ( strncmp(symAction, "hide$", 5) == 0 ) { - if ( _s_logHashtable ) fprintf(stderr, " adding %s to ignore set for %s\n", symName, this->path()); - _ignoreExports.insert(strdup(symName)); - return; - } - else if ( strncmp(symAction, "add$", 4) == 0 ) { - this->addSymbol(symName, weakDef, false, 0); - return; - } - else if ( strncmp(symAction, "install_name$", 13) == 0 ) { - _dylibInstallPath = symName; - _installPathOverride = true; - // CoreGraphics redirects to ApplicationServices, but with wrong compat version - if ( strcmp(_dylibInstallPath, "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices") == 0 ) - _dylibCompatibilityVersion = parseVersionNumber32("1.0"); - return; - } - else if ( strncmp(symAction, "compatibility_version$", 22) == 0 ) { - _dylibCompatibilityVersion = parseVersionNumber32(symName); - return; - } - else { - warning("bad symbol action: %s in dylib %s", name, this->path()); - } - } - } - } - else { - warning("bad symbol condition: %s in dylib %s", name, this->path()); - } - } - - // add symbol as possible export if we are not supposed to ignore it - if ( _ignoreExports.count(name) == 0 ) { - AtomAndWeak bucket; - bucket.atom = NULL; - bucket.weakDef = weakDef; - bucket.tlv = tlv; - bucket.address = address; - if ( _s_logHashtable ) fprintf(stderr, " adding %s to hash table for %s\n", name, this->path()); - _atoms[strdup(name)] = bucket; - } -} - - -template -bool File::forEachAtom(ld::File::AtomHandler& handler) const -{ - handler.doFile(*this); - // if doing flatnamespace and need all this dylib's imports resolve - // add atom which references alls undefines in this dylib - if ( _importAtom != NULL ) { - handler.doAtom(*_importAtom); - return true; - } - return false; -} - - -template -std::pair File::hasWeakDefinitionImpl(const char* name) const -{ - const auto pos = _atoms.find(name); - if ( pos != _atoms.end() ) - return std::make_pair(true, pos->second.weakDef); - - // look in children that I re-export - for (const auto &dep : _dependentDylibs) { - if ( dep.reExport ) { - auto ret = dep.dylib->hasWeakDefinitionImpl(name); - if ( ret.first ) - return ret; - } - } - return std::make_pair(false, false); -} - - -template -bool File::hasWeakDefinition(const char* name) const -{ - // if supposed to ignore this export, then pretend I don't have it - if ( _ignoreExports.count(name) != 0 ) - return false; - - return hasWeakDefinitionImpl(name).second; -} - - -// If only weak_import symbols are used, linker should use LD_LOAD_WEAK_DYLIB -template -bool File::allSymbolsAreWeakImported() const -{ - bool foundNonWeakImport = false; - bool foundWeakImport = false; - //fprintf(stderr, "%s:\n", this->path()); - for (typename NameToAtomMap::const_iterator it = _atoms.begin(); it != _atoms.end(); ++it) { - const ld::Atom* atom = it->second.atom; - if ( atom != NULL ) { - if ( atom->weakImported() ) - foundWeakImport = true; - else - foundNonWeakImport = true; - //fprintf(stderr, " weak_import=%d, name=%s\n", atom->weakImported(), it->first); - } - } - - // don't automatically weak link dylib with no imports - // so at least one weak import symbol and no non-weak-imported symbols must be found - return foundWeakImport && !foundNonWeakImport; -} - - -template -bool File::containsOrReExports(const char* name, bool& weakDef, bool& tlv, uint64_t& defAddress) const -{ - if ( _ignoreExports.count(name) != 0 ) - return false; - - // check myself - const auto pos = _atoms.find(name); - if ( pos != _atoms.end() ) { - weakDef = pos->second.weakDef; - tlv = pos->second.tlv; - defAddress = pos->second.address; - return true; - } - - // check dylibs I re-export - for (const auto &dep : _dependentDylibs) { - if ( dep.reExport && !dep.dylib->implicitlyLinked() ) { - if ( dep.dylib->containsOrReExports(name, weakDef, tlv, defAddress) ) - return true; - } - } - - return false; -} - - -template -bool File::justInTimeforEachAtom(const char* name, ld::File::AtomHandler& handler) const -{ - // if supposed to ignore this export, then pretend I don't have it - if ( _ignoreExports.count(name) != 0 ) - return false; - - - AtomAndWeak bucket; - if ( this->containsOrReExports(name, bucket.weakDef, bucket.tlv, bucket.address) ) { - bucket.atom = new ExportAtom(*this, name, bucket.weakDef, bucket.tlv, bucket.address); - _atoms[name] = bucket; - _providedAtom = true; - if ( _s_logHashtable ) fprintf(stderr, "getJustInTimeAtomsFor: %s found in %s\n", name, this->path()); - // call handler with new export atom - handler.doAtom(*bucket.atom); - return true; - } - - return false; -} - - - -template -bool File::isPublicLocation(const char* pth) -{ - // -no_implicit_dylibs disables this optimization - if ( ! _implicitlyLinkPublicDylibs ) - return false; - - // /usr/lib is a public location - if ( (strncmp(pth, "/usr/lib/", 9) == 0) && (strchr(&pth[9], '/') == NULL) ) - return true; - - // /System/Library/Frameworks/ is a public location - if ( strncmp(pth, "/System/Library/Frameworks/", 27) == 0 ) { - const char* frameworkDot = strchr(&pth[27], '.'); - // but only top level framework - // /System/Library/Frameworks/Foo.framework/Versions/A/Foo ==> true - // /System/Library/Frameworks/Foo.framework/Resources/libBar.dylib ==> false - // /System/Library/Frameworks/Foo.framework/Frameworks/Bar.framework/Bar ==> false - // /System/Library/Frameworks/Foo.framework/Frameworks/Xfoo.framework/XFoo ==> false - if ( frameworkDot != NULL ) { - int frameworkNameLen = frameworkDot - &pth[27]; - if ( strncmp(&pth[strlen(pth)-frameworkNameLen-1], &pth[26], frameworkNameLen+1) == 0 ) - return true; - } - } - - return false; -} - -template -void File::processIndirectLibraries(ld::dylib::File::DylibHandler* handler, bool addImplicitDylibs) -{ - // only do this once - if ( _indirectDylibsProcessed ) - return; - const static bool log = false; - if ( log ) fprintf(stderr, "processIndirectLibraries(%s)\n", this->installPath()); - if ( _linkingFlat ) { - for (typename std::vector::iterator it = _dependentDylibs.begin(); it != _dependentDylibs.end(); it++) { - it->dylib = (File*)handler->findDylib(it->path, this->path()); - } - } - else if ( _noRexports ) { - // MH_NO_REEXPORTED_DYLIBS bit set, then nothing to do - } - else { - // two-level, might have re-exports - for (typename std::vector::iterator it = _dependentDylibs.begin(); it != _dependentDylibs.end(); it++) { - if ( it->reExport ) { - if ( log ) fprintf(stderr, "processIndirectLibraries() parent=%s, child=%s\n", this->installPath(), it->path); - // a LC_REEXPORT_DYLIB, LC_SUB_UMBRELLA or LC_SUB_LIBRARY says we re-export this child - it->dylib = (File*)handler->findDylib(it->path, this->path()); - if ( it->dylib->hasPublicInstallName() && !it->dylib->wrongOS() ) { - // promote this child to be automatically added as a direct dependent if this already is - if ( (this->explicitlyLinked() || this->implicitlyLinked()) && (strcmp(it->path,it->dylib->installPath()) == 0) ) { - if ( log ) fprintf(stderr, "processIndirectLibraries() implicitly linking %s\n", it->dylib->installPath()); - it->dylib->setImplicitlyLinked(); - } - else if ( it->dylib->explicitlyLinked() || it->dylib->implicitlyLinked() ) { - if ( log ) fprintf(stderr, "processIndirectLibraries() parent is not directly linked, but child is, so no need to re-export child\n"); - } - else { - if ( log ) fprintf(stderr, "processIndirectLibraries() parent is not directly linked, so parent=%s will re-export child=%s\n", this->installPath(), it->path); - } - } - else { - // add all child's symbols to me - if ( log ) fprintf(stderr, "processIndirectLibraries() child is not public, so parent=%s will re-export child=%s\n", this->installPath(), it->path); - } - } - else if ( !_explictReExportFound ) { - // see if child contains LC_SUB_FRAMEWORK with my name - it->dylib = (File*)handler->findDylib(it->path, this->path()); - const char* parentUmbrellaName = it->dylib->parentUmbrella(); - if ( parentUmbrellaName != NULL ) { - const char* parentName = this->path(); - const char* lastSlash = strrchr(parentName, '/'); - if ( (lastSlash != NULL) && (strcmp(&lastSlash[1], parentUmbrellaName) == 0) ) { - // add all child's symbols to me - it->reExport = true; - if ( log ) fprintf(stderr, "processIndirectLibraries() umbrella=%s will re-export child=%s\n", this->installPath(), it->path); - } - } - } - } - } - - // check for re-export cycles - ReExportChain chain; - chain.prev = NULL; - chain.file = this; - this->assertNoReExportCycles(&chain); - - _indirectDylibsProcessed = true; -} - -template -void File::assertNoReExportCycles(ReExportChain* prev) const -{ - // recursively check my re-exported dylibs - ReExportChain chain; - chain.prev = prev; - chain.file = this; - for (const auto &dep : _dependentDylibs) { - if ( dep.reExport ) { - ld::File* child = dep.dylib; - // check child is not already in chain - for (ReExportChain* p = prev; p != nullptr; p = p->prev) { - if ( p->file == child ) { - throwf("cycle in dylib re-exports with %s and %s", child->path(), this->path()); - } - } - if ( dep.dylib != nullptr ) - dep.dylib->assertNoReExportCycles(&chain); - } - } -} - - template class Parser { public: - typedef typename A::P P; - - static bool validFile(const uint8_t* fileContent, bool executableOrDyliborBundle); - static const char* fileKind(const uint8_t* fileContent); - static ld::dylib::File* parse(const uint8_t* fileContent, uint64_t fileLength, - const char* path, time_t mTime, - ld::File::Ordinal ordinal, const Options& opts, bool indirectDylib) { - return new File(fileContent, fileLength, path, mTime, - ordinal, opts.flatNamespace(), - opts.linkingMainExecutable(), - opts.implicitlyLinkIndirectPublicDylibs(), - opts.platform(), - opts.minOSversion(), - opts.allowSimulatorToLinkWithMacOSX(), - opts.addVersionLoadCommand(), - opts.targetIOSSimulator(), - opts.logAllFiles(), - opts.installPath(), - indirectDylib, - opts.outputKind() == Options::kPreload, - opts.bundleBitcode()); - } + using P = typename A::P; + + static bool validFile(const uint8_t* fileContent, bool executableOrDyliborBundle, bool subTypeMustMatch=false, uint32_t subType=0); + static const char* fileKind(const uint8_t* fileContent); + static ld::dylib::File* parse(const uint8_t* fileContent, uint64_t fileLength, const char* path, + time_t mTime, ld::File::Ordinal ordinal, const Options& opts, + bool indirectDylib) + { + return new File(fileContent, fileLength, path, mTime, ordinal, opts.flatNamespace(), + opts.linkingMainExecutable(), opts.implicitlyLinkIndirectPublicDylibs(), + opts.platform(), opts.minOSversion(), + opts.allowSimulatorToLinkWithMacOSX(), opts.addVersionLoadCommand(), + opts.targetIOSSimulator(), opts.logAllFiles(), opts.installPath(), + indirectDylib, opts.outputKind() == Options::kPreload, opts.bundleBitcode()); + } }; template <> -bool Parser::validFile(const uint8_t* fileContent, bool executableOrDyliborBundle) +bool Parser::validFile(const uint8_t* fileContent, bool executableOrDyliborBundle, bool subTypeMustMatch, uint32_t subType) { - const macho_header

* header = (const macho_header

*)fileContent; + const auto* header = reinterpret_cast*>(fileContent); if ( header->magic() != MH_MAGIC ) return false; if ( header->cputype() != CPU_TYPE_I386 ) @@ -1045,9 +592,9 @@ bool Parser::validFile(const uint8_t* fileContent, bool executableOrDylibor } template <> -bool Parser::validFile(const uint8_t* fileContent, bool executableOrDyliborBundle) +bool Parser::validFile(const uint8_t* fileContent, bool executableOrDyliborBundle, bool subTypeMustMatch, uint32_t subType) { - const macho_header

* header = (const macho_header

*)fileContent; + const auto* header = reinterpret_cast*>(fileContent); if ( header->magic() != MH_MAGIC_64 ) return false; if ( header->cputype() != CPU_TYPE_X86_64 ) @@ -1072,13 +619,15 @@ bool Parser::validFile(const uint8_t* fileContent, bool executableOrDyli } template <> -bool Parser::validFile(const uint8_t* fileContent, bool executableOrDyliborBundle) +bool Parser::validFile(const uint8_t* fileContent, bool executableOrDyliborBundle, bool subTypeMustMatch, uint32_t subType) { - const macho_header

* header = (const macho_header

*)fileContent; + const auto* header = reinterpret_cast*>(fileContent); if ( header->magic() != MH_MAGIC ) return false; if ( header->cputype() != CPU_TYPE_ARM ) return false; + if ( subTypeMustMatch && (header->cpusubtype() != subType) ) + return false; switch ( header->filetype() ) { case MH_DYLIB: case MH_DYLIB_STUB: @@ -1101,9 +650,9 @@ bool Parser::validFile(const uint8_t* fileContent, bool executableOrDylibor template <> -bool Parser::validFile(const uint8_t* fileContent, bool executableOrDyliborBundle) +bool Parser::validFile(const uint8_t* fileContent, bool executableOrDyliborBundle, bool subTypeMustMatch, uint32_t subType) { - const macho_header

* header = (const macho_header

*)fileContent; + const auto* header = reinterpret_cast*>(fileContent); if ( header->magic() != MH_MAGIC_64 ) return false; if ( header->cputype() != CPU_TYPE_ARM64 ) @@ -1132,7 +681,7 @@ bool isDylibFile(const uint8_t* fileContent, cpu_type_t* result, cpu_subtype_t* { if ( Parser::validFile(fileContent, false) ) { *result = CPU_TYPE_X86_64; - const macho_header >* header = (const macho_header >*)fileContent; + const auto* header = reinterpret_cast>*>(fileContent); *subResult = header->cpusubtype(); return true; } @@ -1143,7 +692,7 @@ bool isDylibFile(const uint8_t* fileContent, cpu_type_t* result, cpu_subtype_t* } if ( Parser::validFile(fileContent, false) ) { *result = CPU_TYPE_ARM; - const macho_header >* header = (const macho_header >*)fileContent; + const auto* header = reinterpret_cast>*>(fileContent); *subResult = header->cpusubtype(); return true; } @@ -1158,34 +707,34 @@ bool isDylibFile(const uint8_t* fileContent, cpu_type_t* result, cpu_subtype_t* template <> const char* Parser::fileKind(const uint8_t* fileContent) { - const macho_header

* header = (const macho_header

*)fileContent; + const auto* header = reinterpret_cast*>(fileContent); if ( header->magic() != MH_MAGIC ) - return NULL; + return nullptr; if ( header->cputype() != CPU_TYPE_I386 ) - return NULL; + return nullptr; return "i386"; } template <> const char* Parser::fileKind(const uint8_t* fileContent) { - const macho_header

* header = (const macho_header

*)fileContent; + const auto* header = reinterpret_cast*>(fileContent); if ( header->magic() != MH_MAGIC_64 ) - return NULL; + return nullptr; if ( header->cputype() != CPU_TYPE_X86_64 ) - return NULL; + return nullptr; return "x86_64"; } template <> const char* Parser::fileKind(const uint8_t* fileContent) { - const macho_header

* header = (const macho_header

*)fileContent; + const auto* header = reinterpret_cast*>(fileContent); if ( header->magic() != MH_MAGIC ) - return NULL; + return nullptr; if ( header->cputype() != CPU_TYPE_ARM ) - return NULL; - for (const ArchInfo* t=archInfoArray; t->archName != NULL; ++t) { + return nullptr; + for (const auto* t = archInfoArray; t->archName != nullptr; ++t) { if ( (t->cpuType == CPU_TYPE_ARM) && ((cpu_subtype_t)header->cpusubtype() == t->cpuSubType) ) { return t->archName; } @@ -1197,11 +746,11 @@ const char* Parser::fileKind(const uint8_t* fileContent) template <> const char* Parser::fileKind(const uint8_t* fileContent) { - const macho_header

* header = (const macho_header

*)fileContent; + const auto* header = reinterpret_cast*>(fileContent); if ( header->magic() != MH_MAGIC_64 ) - return NULL; + return nullptr; if ( header->cputype() != CPU_TYPE_ARM64 ) - return NULL; + return nullptr; return "arm64"; } #endif @@ -1225,48 +774,47 @@ const char* archName(const uint8_t* fileContent) return Parser::fileKind(fileContent); } #endif - return NULL; + return nullptr; } // // main function used by linker to instantiate ld::Files // -ld::dylib::File* parse(const uint8_t* fileContent, uint64_t fileLength, - const char* path, time_t modTime, const Options& opts, ld::File::Ordinal ordinal, - bool bundleLoader, bool indirectDylib) +ld::dylib::File* parse(const uint8_t* fileContent, uint64_t fileLength, const char* path, + time_t modTime, const Options& opts, ld::File::Ordinal ordinal, + bool bundleLoader, bool indirectDylib) { + bool subTypeMustMatch = opts.enforceDylibSubtypesMatch(); switch ( opts.architecture() ) { #if SUPPORT_ARCH_x86_64 case CPU_TYPE_X86_64: - if ( Parser::validFile(fileContent, bundleLoader) ) + if ( Parser::validFile(fileContent, bundleLoader, subTypeMustMatch, opts.subArchitecture()) ) return Parser::parse(fileContent, fileLength, path, modTime, ordinal, opts, indirectDylib); break; #endif #if SUPPORT_ARCH_i386 case CPU_TYPE_I386: - if ( Parser::validFile(fileContent, bundleLoader) ) + if ( Parser::validFile(fileContent, bundleLoader, subTypeMustMatch, opts.subArchitecture()) ) return Parser::parse(fileContent, fileLength, path, modTime, ordinal, opts, indirectDylib); break; #endif #if SUPPORT_ARCH_arm_any case CPU_TYPE_ARM: - if ( Parser::validFile(fileContent, bundleLoader) ) + if ( Parser::validFile(fileContent, bundleLoader, subTypeMustMatch, opts.subArchitecture()) ) return Parser::parse(fileContent, fileLength, path, modTime, ordinal, opts, indirectDylib); break; #endif #if SUPPORT_ARCH_arm64 case CPU_TYPE_ARM64: - if ( Parser::validFile(fileContent, bundleLoader) ) + if ( Parser::validFile(fileContent, bundleLoader, subTypeMustMatch, opts.subArchitecture()) ) return Parser::parse(fileContent, fileLength, path, modTime, ordinal, opts, indirectDylib); break; #endif } - return NULL; + return nullptr; } }; // namespace dylib }; // namespace mach_o - - diff --git a/src/ld/parsers/macho_dylib_file.h b/src/ld/parsers/macho_dylib_file.h index 4d093f1..ba7b8ff 100644 --- a/src/ld/parsers/macho_dylib_file.h +++ b/src/ld/parsers/macho_dylib_file.h @@ -37,9 +37,9 @@ extern bool isDylibFile(const uint8_t* fileContent, cpu_type_t* result, cpu_subt extern const char* archName(const uint8_t* fileContent); -extern ld::dylib::File* parse(const uint8_t* fileContent, uint64_t fileLength, const char* path, - time_t modTime, const Options& opts, ld::File::Ordinal ordinal, - bool bundleLoader, bool indirectDylib); +extern ld::dylib::File* parse(const uint8_t* fileContent, uint64_t fileLength, const char* path, + time_t modTime, const Options& opts, ld::File::Ordinal ordinal, + bool bundleLoader, bool indirectDylib); } // namespace dylib } // namespace mach_o diff --git a/src/ld/parsers/macho_relocatable_file.cpp b/src/ld/parsers/macho_relocatable_file.cpp index 2fa41b9..9689bf0 100644 --- a/src/ld/parsers/macho_relocatable_file.cpp +++ b/src/ld/parsers/macho_relocatable_file.cpp @@ -1089,7 +1089,8 @@ public: TargetDesc& target); uint32_t tentativeDefinitionCount() { return _tentativeDefinitionCount; } uint32_t absoluteSymbolCount() { return _absoluteSymbolCount; } - + + uint32_t fileLength() const { return _fileLength; } bool hasStubsSection() { return (_stubsSectionNum != 0); } unsigned int stubsSectionNum() { return _stubsSectionNum; } void addDtraceExtraInfos(const SourceLocation& src, const char* provider); @@ -1101,6 +1102,8 @@ public: bool verboseOptimizationHints() { return _verboseOptimizationHints; } bool neverConvertDwarf() { return _neverConvertDwarf; } bool armUsesZeroCostExceptions() { return _armUsesZeroCostExceptions; } + uint8_t maxDefaultCommonAlignment() { return _maxDefaultCommonAlignment; } + macho_data_in_code_entry

* dataInCodeStart() { return _dataInCodeStart; } macho_data_in_code_entry

* dataInCodeEnd() { return _dataInCodeEnd; } @@ -1256,6 +1259,7 @@ private: bool _ignoreMismatchPlatform; bool _treateBitcodeAsData; bool _usingBitcode; + uint8_t _maxDefaultCommonAlignment; unsigned int _stubsSectionNum; const macho_section

* _stubsMachOSection; std::vector _dtraceProviderInfo; @@ -1474,11 +1478,11 @@ bool Parser::getNonLocalSymbols(const uint8_t* fileContent, std::vectornsyms(); const macho_nlist

* symbols = (const macho_nlist

*)(fileContent + symtab->symoff()); const char* strings = (char*)fileContent + symtab->stroff(); - for (uint32_t i = 0; i < symbolCount; ++i) { + for (uint32_t j = 0; j < symbolCount; ++j) { // ignore stabs and count only ext symbols - if ( (symbols[i].n_type() & N_STAB) == 0 && - (symbols[i].n_type() & N_EXT) != 0 ) { - const char* symName = &strings[symbols[i].n_strx()]; + if ( (symbols[j].n_type() & N_STAB) == 0 && + (symbols[j].n_type() & N_EXT) != 0 ) { + const char* symName = &strings[symbols[j].n_strx()]; syms.push_back(symName); } } @@ -1726,6 +1730,7 @@ ld::relocatable::File* Parser::parse(const ParserOptions& opts) printf("%s\n", _path); _armUsesZeroCostExceptions = opts.armUsesZeroCostExceptions; + _maxDefaultCommonAlignment = opts.maxDefaultCommonAlignment; // parse start of mach-o file if ( ! parseLoadCommands(opts.platform, opts.minOSVersion, opts.simulator, opts.ignoreMismatchPlatform) ) @@ -2077,12 +2082,13 @@ bool Parser::parseLoadCommands(Options::Platform platform, uint32_t linkMinOS lcPlatform = Options::platformForLoadCommand(cmd->cmd()); _file->_minOSVersion = ((macho_version_min_command

*)cmd)->version(); break; + case macho_segment_command

::CMD: + if ( segment != NULL ) + throw "more than one LC_SEGMENT found in object file"; + segment = (macho_segment_command

*)cmd; + break; default: - if ( cmd->cmd() == macho_segment_command

::CMD ) { - if ( segment != NULL ) - throw "more than one LC_SEGMENT found in object file"; - segment = (macho_segment_command

*)cmd; - } + // ignore unknown load commands break; } cmd = (const macho_load_command

*)(((char*)cmd)+cmd->cmdsize()); @@ -2105,10 +2111,16 @@ bool Parser::parseLoadCommands(Options::Platform platform, uint32_t linkMinOS break; // fall through if the Platform is not Unknown case Options::kPlatformWatchOS: - // WatchOS errors on cross-linking all the time. - throwf("building for %s%s, but linking in object file built for %s,", + // Error when using bitcocde, warning otherwise. + if (_usingBitcode) + throwf("building for %s%s, but linking in object file built for %s,", Options::platformName(platform), (simulator ? " simulator" : ""), Options::platformName(lcPlatform)); + else + warning("URGENT: building for %s%s, but linking in object file (%s) built for %s. " + "Note: This will be an error in the future.", + Options::platformName(platform), (simulator ? " simulator" : ""), path(), + Options::platformName(lcPlatform)); break; #if SUPPORT_APPLE_TV case Options::kPlatform_tvOS: @@ -2145,7 +2157,8 @@ bool Parser::parseLoadCommands(Options::Platform platform, uint32_t linkMinOS throw "missing LC_SEGMENT"; _sectionsStart = (macho_section

*)((char*)segment + sizeof(macho_segment_command

)); _machOSectionsCount = segment->nsects(); - + if ( (sizeof(macho_segment_command

) + _machOSectionsCount * sizeof(macho_section

)) > segment->cmdsize() ) + throw "too many sections for size of LC_SEGMENT command"; return true; } @@ -2453,6 +2466,9 @@ void Parser::makeSections() for (uint32_t i=0; i < _machOSectionsCount; ++i) { const macho_section

* sect = &_sectionsStart[i]; + if ( (sect->offset() + sect->size() > _fileLength) && ((sect->flags() & SECTION_TYPE) != S_ZEROFILL) ) + throwf("section %s/%s extends beyond end of file,", sect->segname(), sect->sectname()); + if ( (sect->flags() & S_ATTR_DEBUG) != 0 ) { if ( strcmp(sect->segname(), "__DWARF") == 0 ) { // note that .o file has dwarf @@ -3152,8 +3168,8 @@ uint32_t TentativeDefinitionSection::appendAtoms(class Parser& parser, uin ++alignP2; } // limit alignment of extremely large commons to 2^15 bytes (8-page) - if ( alignP2 > 15 ) - alignP2 = 15; + if ( alignP2 > parser.maxDefaultCommonAlignment() ) + alignP2 = parser.maxDefaultCommonAlignment(); Atom* allocatedSpace = (Atom*)p; new (allocatedSpace) Atom(*this, parser.nameFromSymbol(sym), (pint_t)ULLONG_MAX, size, ld::Atom::definitionTentative, ld::Atom::combineByName, @@ -3950,6 +3966,7 @@ bool Parser::read_comp_unit(const char ** name, const char ** comp_dir, const uint8_t * debug_info; const uint8_t * debug_abbrev; const uint8_t * di; + const uint8_t * next_cu; const uint8_t * da; const uint8_t * end; const uint8_t * enda; @@ -3967,108 +3984,122 @@ bool Parser::read_comp_unit(const char ** name, const char ** comp_dir, if ( (_file->_dwarfDebugInfoSect == NULL) || (_file->_dwarfDebugAbbrevSect == NULL) ) return false; - debug_info = (uint8_t*)_file->fileContent() + _file->_dwarfDebugInfoSect->offset(); - debug_abbrev = (uint8_t*)_file->fileContent() + _file->_dwarfDebugAbbrevSect->offset(); - di = debug_info; - if (_file->_dwarfDebugInfoSect->size() < 12) - /* Too small to be a real debug_info section. */ - return false; - sz = A::P::E::get32(*(uint32_t*)di); - di += 4; - dwarf64 = sz == 0xffffffff; - if (dwarf64) - sz = A::P::E::get64(*(uint64_t*)di), di += 8; - else if (sz > 0xffffff00) - /* Unknown dwarf format. */ - return false; - - /* Verify claimed size. */ - if (sz + (di - debug_info) > _file->_dwarfDebugInfoSect->size() || sz <= (dwarf64 ? 23 : 11)) + /* Too small to be a real debug_info section. */ return false; - vers = A::P::E::get16(*(uint16_t*)di); - if (vers < 2 || vers > 4) - /* DWARF version wrong for this code. - Chances are we could continue anyway, but we don't know for sure. */ - return false; - di += 2; - - /* Find the debug_abbrev section. */ - abbrev_base = dwarf64 ? A::P::E::get64(*(uint64_t*)di) : A::P::E::get32(*(uint32_t*)di); - di += dwarf64 ? 8 : 4; - - if (abbrev_base > _file->_dwarfDebugAbbrevSect->size()) - return false; - da = debug_abbrev + abbrev_base; - enda = debug_abbrev + _file->_dwarfDebugAbbrevSect->size(); - - address_size = *di++; - - /* Find the abbrev number we're looking for. */ - end = di + sz; - abbrev = read_uleb128 (&di, end); - if (abbrev == (uint64_t) -1) - return false; - - /* Skip through the debug_abbrev section looking for that abbrev. */ - for (;;) - { - uint64_t this_abbrev = read_uleb128 (&da, enda); - uint64_t attr; - - if (this_abbrev == abbrev) - /* This is almost always taken. */ - break; - skip_leb128 (&da, enda); /* Skip the tag. */ - if (da == enda) - return false; - da++; /* Skip the DW_CHILDREN_* value. */ - - do { - attr = read_uleb128 (&da, enda); - skip_leb128 (&da, enda); - } while (attr != 0 && attr != (uint64_t) -1); - if (attr != 0) - return false; - } - - /* Check that the abbrev is one for a DW_TAG_compile_unit. */ - if (read_uleb128 (&da, enda) != DW_TAG_compile_unit) - return false; - if (da == enda) - return false; - da++; /* Skip the DW_CHILDREN_* value. */ - - /* Now, go through the DIE looking for DW_AT_name, - DW_AT_comp_dir, and DW_AT_stmt_list. */ - for (;;) - { - uint64_t attr = read_uleb128 (&da, enda); - uint64_t form = read_uleb128 (&da, enda); - - if (attr == (uint64_t) -1) - return false; - else if (attr == 0) - return true; - if (form == DW_FORM_indirect) - form = read_uleb128 (&di, end); - - switch (attr) { - case DW_AT_name: - *name = getDwarfString(form, di); - break; - case DW_AT_comp_dir: - *comp_dir = getDwarfString(form, di); - break; - case DW_AT_stmt_list: - *stmt_list = getDwarfOffset(form, di, dwarf64); - break; - default: - if (! skip_form (&di, end, form, address_size, dwarf64)) - return false; - } - } + debug_info = (uint8_t*)_file->fileContent() + _file->_dwarfDebugInfoSect->offset(); + debug_abbrev = (uint8_t*)_file->fileContent() + _file->_dwarfDebugAbbrevSect->offset(); + next_cu = debug_info; + + while ((uint64_t)(next_cu - debug_info) < _file->_dwarfDebugInfoSect->size()) { + di = next_cu; + sz = A::P::E::get32(*(uint32_t*)di); + di += 4; + dwarf64 = sz == 0xffffffff; + if (dwarf64) + sz = A::P::E::get64(*(uint64_t*)di), di += 8; + else if (sz > 0xffffff00) + /* Unknown dwarf format. */ + return false; + + /* Verify claimed size. */ + if (sz + (di - debug_info) > _file->_dwarfDebugInfoSect->size() || sz <= (dwarf64 ? 23 : 11)) + return false; + + next_cu = di + sz; + + vers = A::P::E::get16(*(uint16_t*)di); + if (vers < 2 || vers > 4) + /* DWARF version wrong for this code. + Chances are we could continue anyway, but we don't know for sure. */ + return false; + di += 2; + + /* Find the debug_abbrev section. */ + abbrev_base = dwarf64 ? A::P::E::get64(*(uint64_t*)di) : A::P::E::get32(*(uint32_t*)di); + di += dwarf64 ? 8 : 4; + + if (abbrev_base > _file->_dwarfDebugAbbrevSect->size()) + return false; + da = debug_abbrev + abbrev_base; + enda = debug_abbrev + _file->_dwarfDebugAbbrevSect->size(); + + address_size = *di++; + + /* Find the abbrev number we're looking for. */ + end = di + sz; + abbrev = read_uleb128 (&di, end); + if (abbrev == (uint64_t) -1) + return false; + + /* Skip through the debug_abbrev section looking for that abbrev. */ + for (;;) + { + uint64_t this_abbrev = read_uleb128 (&da, enda); + uint64_t attr; + + if (this_abbrev == abbrev) + /* This is almost always taken. */ + break; + skip_leb128 (&da, enda); /* Skip the tag. */ + if (da == enda) + return false; + da++; /* Skip the DW_CHILDREN_* value. */ + + do { + attr = read_uleb128 (&da, enda); + skip_leb128 (&da, enda); + } while (attr != 0 && attr != (uint64_t) -1); + if (attr != 0) + return false; + } + + /* Check that the abbrev is one for a DW_TAG_compile_unit. */ + if (read_uleb128 (&da, enda) != DW_TAG_compile_unit) + return false; + if (da == enda) + return false; + da++; /* Skip the DW_CHILDREN_* value. */ + + /* Now, go through the DIE looking for DW_AT_name, + DW_AT_comp_dir, and DW_AT_stmt_list. */ + bool skip_to_next_cu = false; + while (!skip_to_next_cu) { + + uint64_t attr = read_uleb128 (&da, enda); + uint64_t form = read_uleb128 (&da, enda); + + if (attr == (uint64_t) -1) + return false; + else if (attr == 0) + return true; + if (form == DW_FORM_indirect) + form = read_uleb128 (&di, end); + + switch (attr) { + case DW_AT_name: + *name = getDwarfString(form, di); + /* Swift object files may contain two CUs: One + describes the Swift code, one is created by the + clang importer. Skip over the CU created by the + clang importer as it may be empty. */ + if (std::string(*name) == "") + skip_to_next_cu = true; + break; + case DW_AT_comp_dir: + *comp_dir = getDwarfString(form, di); + break; + case DW_AT_stmt_list: + *stmt_list = getDwarfOffset(form, di, dwarf64); + break; + default: + if (! skip_form (&di, end, form, address_size, dwarf64)) + return false; + } + } + } + return false; } @@ -4366,8 +4397,9 @@ void CFISection::cfiParse(class Parser& parser, uint8_t* buffer, libunwind::CFI_Atom_Info::OAS>::CFI_Atom_Info cfiArray[], uint32_t& count, const pint_t cuStarts[], uint32_t cuCount) { + const uint32_t sectionSize = this->_machOSection->size(); // copy __eh_frame data to buffer - memcpy(buffer, file().fileContent() + this->_machOSection->offset(), this->_machOSection->size()); + memcpy(buffer, file().fileContent() + this->_machOSection->offset(), sectionSize); // and apply relocations const macho_relocation_info

* relocs = (macho_relocation_info

*)(file().fileContent() + this->_machOSection->reloff()); @@ -4393,6 +4425,8 @@ void CFISection::cfiParse(class Parser& parser, uint8_t* buffer, fprintf(stderr, "CFISection::cfiParse() unexpected relocation type at r_address=0x%08X\n", reloc->r_address()); break; } + if ( reloc->r_address() > sectionSize ) + throwf("malformed __eh_frame relocation, offset (0x%08X) is beyond end of section,", reloc->r_address()); uint64_t* p64; uint32_t* p32; switch ( reloc->r_length() ) { @@ -4476,7 +4510,8 @@ void CFISection::cfiParse(class Parser& parser, uint8_t* buffer, uint32_t& count, const pint_t cuStarts[], uint32_t cuCount) { // copy __eh_frame data to buffer - memcpy(buffer, file().fileContent() + this->_machOSection->offset(), this->_machOSection->size()); + const uint32_t sectionSize = this->_machOSection->size(); + memcpy(buffer, file().fileContent() + this->_machOSection->offset(), sectionSize); // and apply relocations const macho_relocation_info

* relocs = (macho_relocation_info

*)(file().fileContent() + this->_machOSection->reloff()); @@ -4508,6 +4543,8 @@ void CFISection::cfiParse(class Parser& parser, uint8_t* buffer, fprintf(stderr, "CFISection::cfiParse() unexpected relocation type at r_address=0x%08X\n", reloc->r_address()); break; } + if ( reloc->r_address() > sectionSize ) + throwf("malformed __eh_frame relocation, offset (0x%08X) is beyond end of section,", reloc->r_address()); switch ( reloc->r_length() ) { case 3: E::set64(*p64, value + addend64); @@ -5070,9 +5107,14 @@ void CUSection::parse(class Parser& parser, uint32_t cnt, Info array[]) } // scan relocs, extern relocs are needed for personality references (possibly for function/lsda refs??) + const uint32_t sectionSize = this->_machOSection->size(); const macho_relocation_info

* relocs = (macho_relocation_info

*)(this->file().fileContent() + this->_machOSection->reloff()); const macho_relocation_info

* relocsEnd = &relocs[this->_machOSection->nreloc()]; for (const macho_relocation_info

* reloc = relocs; reloc < relocsEnd; ++reloc) { + if ( reloc->r_address() & R_SCATTERED ) + continue; + if ( reloc->r_address() > sectionSize ) + throwf("malformed __compact_unwind relocation, offset (0x%08X) is beyond end of section,", reloc->r_address()); if ( reloc->r_extern() ) { // only expect external relocs on some colummns if ( (reloc->r_address() % sizeof(macho_compact_unwind_entry

)) == macho_compact_unwind_entry

::personalityFieldOffset() ) { @@ -6309,10 +6351,14 @@ bool Section::addRelocFixup(class Parser& parser, const macho_re } else { parser.findTargetFromAddressAndSectionNum(contentValue, nextReloc->r_symbolnum(), toTarget); - useDirectBinding = (toTarget.atom->scope() == ld::Atom::scopeTranslationUnit); + useDirectBinding = (toTarget.atom->scope() == ld::Atom::scopeTranslationUnit) || ((toTarget.atom->combine() == ld::Atom::combineByNameAndContent) || (toTarget.atom->combine() == ld::Atom::combineByNameAndReferences)); + } + if ( useDirectBinding ) { + if ( (toTarget.atom->combine() == ld::Atom::combineByNameAndContent) || (toTarget.atom->combine() == ld::Atom::combineByNameAndReferences) ) + parser.addFixup(src, ld::Fixup::k1of4, ld::Fixup::kindSetTargetAddress, ld::Fixup::bindingByContentBound, toTarget.atom); + else + parser.addFixup(src, ld::Fixup::k1of4, ld::Fixup::kindSetTargetAddress, toTarget.atom); } - if ( useDirectBinding ) - parser.addFixup(src, ld::Fixup::k1of4, ld::Fixup::kindSetTargetAddress, toTarget.atom); else parser.addFixup(src, ld::Fixup::k1of4, ld::Fixup::kindSetTargetAddress, toTarget.weakImport, toTarget.name); parser.addFixup(src, ld::Fixup::k2of4, ld::Fixup::kindAddAddend, toTarget.addend); @@ -7519,6 +7565,8 @@ template void Section::makeFixups(class Parser& parser, const struct Parser::CFI_CU_InfoArrays&) { const macho_section

* sect = this->machoSection(); + if ( sect->reloff() + (sect->nreloc() * sizeof(macho_relocation_info

)) > parser.fileLength() ) + throwf("relocations for section %s/%s extends beyond end of file,", sect->segname(), Section::makeSectionName(sect) ); const macho_relocation_info

* relocs = (macho_relocation_info

*)(file().fileContent() + sect->reloff()); const uint32_t relocCount = sect->nreloc(); for (uint32_t r = 0; r < relocCount; ++r) { @@ -7558,7 +7606,8 @@ void Section::makeFixups(class Parser& parser, const struct Parser::CFI Atom* end = &_endAtoms[-1]; for(Atom* p = _beginAtoms; p < end; ++p) { Atom* nextAtom = &p[1]; - if ( _altEntries.count(nextAtom) != 0 ) { + // support alt_entry aliases (alias process already added followOn, don't repeat) + if ( (_altEntries.count(nextAtom) != 0) && (p->_objAddress != nextAtom->_objAddress) ) { typename Parser::SourceLocation src(p, 0); parser.addFixup(src, ld::Fixup::k1of1, ld::Fixup::kindNoneFollowOn, nextAtom); typename Parser::SourceLocation src2(nextAtom, 0); diff --git a/src/ld/parsers/macho_relocatable_file.h b/src/ld/parsers/macho_relocatable_file.h index ca1e5cc..e31f7f9 100644 --- a/src/ld/parsers/macho_relocatable_file.h +++ b/src/ld/parsers/macho_relocatable_file.h @@ -49,6 +49,7 @@ struct ParserOptions { ld::relocatable::File::SourceKind srcKind; bool treateBitcodeAsData; bool usingBitcode; + uint8_t maxDefaultCommonAlignment; }; extern ld::relocatable::File* parse(const uint8_t* fileContent, uint64_t fileLength, diff --git a/src/ld/parsers/textstub_dylib_file.cpp b/src/ld/parsers/textstub_dylib_file.cpp index 825cb05..fb452d8 100644 --- a/src/ld/parsers/textstub_dylib_file.cpp +++ b/src/ld/parsers/textstub_dylib_file.cpp @@ -32,6 +32,7 @@ #include "bitcode.hpp" #include "MachOFileAbstraction.hpp" #include "MachOTrie.hpp" +#include "generic_dylib_file.hpp" #include "textstub_dylib_file.hpp" namespace { @@ -65,7 +66,7 @@ public: size_t size() const { return _size; } - std::string str() const { return std::move(std::string(_p, _size)); } + std::string str() const { return std::string(_p, _size); } bool empty() const { return _size == 0; } @@ -195,33 +196,6 @@ struct DynamicLibrary { _objcConstraint(ld::File::objcConstraintNone) {} }; -static uint32_t parseVersionNumber32(Token token) { - if ( token.size() >= 128 ) - throwf("malformed version number"); - - char buffer[128]; - uint32_t x = 0; - uint32_t y = 0; - uint32_t z = 0; - char* end; - - // Make a null-terminated string. - ::memcpy(buffer, token.data(), token.size()); - buffer[token.size()] = '\0'; - - x = strtoul(buffer, &end, 10); - if ( *end == '.' ) { - y = strtoul(&end[1], &end, 10); - if ( *end == '.' ) { - z = strtoul(&end[1], &end, 10); - } - } - if ( (x > 0xffff) || (y > 0xff) || (z > 0xff) ) - throwf("malformed 32-bit x.y.z version number: %s", buffer); - - return (x << 16) | ( y << 8 ) | z; -} - /// /// A simple text-based dynamic library file parser. /// @@ -310,20 +284,24 @@ class TBDFile { } } - bool parseArchFlowSequence(Token archName) { + std::vector parseArchFlowSequence() { + std::vector availabledArchitectures; expectToken("archs"); + parseFlowSequence([&](Token name) { + availabledArchitectures.emplace_back(name.str()); + }); + return availabledArchitectures; + } - // x86_64h fails to link against text based stubs - if ( archName == "x86_64h" ) - archName = "x86_64"; + bool parseArchFlowSequence(std::string &selectedArchName) { + auto availabledArchitectures = parseArchFlowSequence(); - bool foundArch = false; - parseFlowSequence([&](Token name) { - if ( name == archName ) - foundArch = true; - }); + for (const auto &archName : availabledArchitectures) { + if (archName == selectedArchName) + return true; + } - return foundArch; + return false; } void parsePlatform(DynamicLibrary& lib) { @@ -352,6 +330,18 @@ class TBDFile { throwf("no install name specified"); } + uint32_t parseVersionNumber32(Token token) { + if ( token.size() >= 128 ) + throwf("malformed version number"); + + // Make a null-terminated string. + char buffer[128]; + ::memcpy(buffer, token.data(), token.size()); + buffer[token.size()] = '\0'; + + return Options::parseVersionNumber32(buffer); + } + void parseCurrentVersion(DynamicLibrary& lib) { if ( !hasOptionalToken("current-version") ) return; @@ -395,7 +385,7 @@ class TBDFile { else throwf("unexpected token: %s", token.str().c_str()); } - void parseExportsBlock(DynamicLibrary& lib, Token archName) { + void parseExportsBlock(DynamicLibrary& lib, std::string &selectedArchName) { if ( !hasOptionalToken("exports") ) return; @@ -403,7 +393,7 @@ class TBDFile { return; while ( true ) { - if ( !parseArchFlowSequence(archName) ) { + if ( !parseArchFlowSequence(selectedArchName) ) { Token token; while ( true ) { token = peek(); @@ -425,8 +415,47 @@ class TBDFile { } } - void parseDocument(DynamicLibrary& lib, Token archName) { - if ( !parseArchFlowSequence(archName) ) + std::vector getCompatibleArchList(std::string &requestedArchName) { + if (requestedArchName == "i386") + return {"i386"}; + else if (requestedArchName == "x86_64" || requestedArchName == "x86_64h") + return {"x86_64", "x86_64h"}; + else if (requestedArchName == "armv7" || requestedArchName == "armv7s") + return {"armv7", "armv7s"}; + else if (requestedArchName == "armv7k") + return {"armv7k"}; + else if (requestedArchName == "arm64") + return {"arm64"}; + else + return {}; + } + + std::string parseAndSelectArchitecture(std::string &requestedArchName) { + auto availabledArchitectures = parseArchFlowSequence(); + + // First try to find an exact match (cpu type and sub-cpu type). + if (std::find(availabledArchitectures.begin(), availabledArchitectures.end(), requestedArchName) + != availabledArchitectures.end()) + return requestedArchName; + + // If there is no exact match, then try to find an ABI compatible slice. + auto compatibleArchitectures = getCompatibleArchList(requestedArchName); + std::vector result; + std::sort(availabledArchitectures.begin(), availabledArchitectures.end()); + std::sort(compatibleArchitectures.begin(), compatibleArchitectures.end()); + std::set_intersection(availabledArchitectures.begin(), availabledArchitectures.end(), + compatibleArchitectures.begin(), compatibleArchitectures.end(), + std::back_inserter(result)); + + if (result.empty()) + return std::string(); + else + return result.front(); + } + + void parseDocument(DynamicLibrary& lib, std::string &requestedArchName) { + auto selectedArchName = parseAndSelectArchitecture(requestedArchName); + if (selectedArchName.empty()) throwf("invalid arch"); parsePlatform(lib); @@ -435,27 +464,27 @@ class TBDFile { parseCompatibilityVersion(lib); parseSwiftVersion(lib); parseObjCConstraint(lib); - parseExportsBlock(lib, archName); + parseExportsBlock(lib, selectedArchName); } public: TBDFile(const char* data, uint64_t size) : _tokenizer(data, size) {} - DynamicLibrary parseFileForArch(Token archName) { + DynamicLibrary parseFileForArch(std::string requestedArchName) { _tokenizer.reset(); DynamicLibrary lib; expectToken("---"); - parseDocument(lib, archName); + parseDocument(lib, requestedArchName); expectToken("..."); - return std::move(lib); + return lib; } - bool validForArch(Token archName) { + bool validForArch(std::string requestedArchName) { _tokenizer.reset(); auto token = next(); if ( token != "---" ) return false; - return parseArchFlowSequence(archName); + return !parseAndSelectArchitecture(requestedArchName).empty(); } void dumpTokens() { @@ -473,52 +502,16 @@ public: namespace textstub { namespace dylib { -// forward reference -template class File; - - -// -// An ExportAtom has no content. It exists so that the linker can track which imported -// symbols came from which dynamic libraries. -// -template -class ExportAtom : public ld::Atom -{ -public: - ExportAtom(const File& f, const char* nm, bool weakDef, bool tlv) - : ld::Atom(f._importProxySection, ld::Atom::definitionProxy, - (weakDef? ld::Atom::combineByName : ld::Atom::combineNever), - ld::Atom::scopeLinkageUnit, - (tlv ? ld::Atom::typeTLV : ld::Atom::typeUnclassified), - symbolTableNotIn, false, false, false, ld::Atom::Alignment(0)), - _file(f), _name(nm) {} - // overrides of ld::Atom - virtual const ld::File* file() const { return &_file; } - virtual const char* name() const { return _name; } - virtual uint64_t size() const { return 0; } - virtual uint64_t objectAddress() const { return 0; } - virtual void copyRawContent(uint8_t buffer[]) const { } - virtual void setScope(Scope) { } - -protected: - typedef typename A::P P; - typedef typename A::P::uint_t pint_t; - - virtual ~ExportAtom() {} - - const File& _file; - const char* _name; -}; - - // // The reader for a dylib extracts all exported symbols names from the memory-mapped // dylib, builds a hash table, then unmaps the file. This is an important memory // savings for large dylibs. // template -class File : public ld::dylib::File +class File final : public generic::dylib::File { + using Base = generic::dylib::File; + public: static bool validFile(const uint8_t* fileContent, bool executableOrDylib); File(const uint8_t* fileContent, uint64_t fileLength, const char* path, @@ -527,91 +520,13 @@ public: cpu_type_t cpuType, const char* archName, uint32_t linkMinOSVersion, bool allowSimToMacOSX, bool addVers, bool buildingForSimulator, bool logAllFiles, const char* installPath, bool indirectDylib); - virtual ~File() {} - - // overrides of ld::File - virtual bool forEachAtom(ld::File::AtomHandler&) const; - virtual bool justInTimeforEachAtom(const char* name, ld::File::AtomHandler&) const; - virtual ld::File::ObjcConstraint objCConstraint() const { return _objcConstraint; } - virtual uint8_t swiftVersion() const { return _swiftVersion; } - - // overrides of ld::dylib::File - virtual void processIndirectLibraries(ld::dylib::File::DylibHandler*, bool); - virtual bool providedExportAtom() const { return _providedAtom; } - virtual const char* parentUmbrella() const { return nullptr; } - virtual const std::vector* allowableClients() const { return _allowableClients.size() != 0 ? &_allowableClients : nullptr; } - virtual bool hasWeakExternals() const { return _hasWeakExports; } - virtual bool deadStrippable() const { return false; } - virtual bool hasPublicInstallName() const{ return _hasPublicInstallName; } - virtual bool hasWeakDefinition(const char* name) const; - virtual bool allSymbolsAreWeakImported() const; - virtual bool installPathVersionSpecific() const { return _installPathOverride; } - // All text-based stubs are per definition AppExtensionSafe. - virtual bool appExtensionSafe() const { return true; }; - virtual ld::Bitcode* getBitcode() const { return _bitcode.get(); } - - -protected: - virtual void assertNoReExportCycles(ReExportChain*) const; + virtual ~File() noexcept {} private: - typedef typename A::P P; - typedef typename A::P::E E; - typedef typename A::P::uint_t pint_t; - - friend class ExportAtom; - - struct CStringHash { - std::size_t operator()(const char* __s) const { - unsigned long __h = 0; - for ( ; *__s; ++__s) - __h = 5 * __h + *__s; - return size_t(__h); - }; - }; - struct AtomAndWeak { ld::Atom* atom; bool weakDef; bool tlv; }; - typedef std::unordered_map NameToAtomMap; - typedef std::unordered_set NameSet; - - struct Dependent { const char* path; File* dylib; }; - - virtual std::pair hasWeakDefinitionImpl(const char* name) const; - virtual bool containsOrReExports(const char* name, bool& weakDef, bool& tlv, uint64_t& address) const; - - void buildExportHashTable(const DynamicLibrary &lib); - bool isPublicLocation(const char* pth); - bool wrongOS() { return _wrongOS; } - void addSymbol(const char* name, bool weak, bool tlv); - - const Options::Platform _platform; - cpu_type_t _cpuType; - const uint32_t _linkMinOSVersion; - const bool _allowSimToMacOSXLinking; - const bool _addVersionLoadCommand; - bool _linkingFlat; - bool _implicitlyLinkPublicDylibs; - ld::File::ObjcConstraint _objcConstraint; - uint8_t _swiftVersion; - ld::Section _importProxySection; - ld::Section _flatDummySection; - std::vector _dependentDylibs; - std::vector _allowableClients; - mutable NameToAtomMap _atoms; - NameSet _ignoreExports; - bool _noRexports; - bool _hasWeakExports; - bool _hasPublicInstallName; - mutable bool _providedAtom; - bool _wrongOS; - bool _installPathOverride; - bool _indirectDylibsProcessed; - std::unique_ptr _bitcode; - static bool _s_logHashtable; -}; - -template -bool File::_s_logHashtable = false; + void buildExportHashTable(const DynamicLibrary &lib); + cpu_type_t _cpuType; +}; template File::File(const uint8_t* fileContent, uint64_t fileLength, const char* path, time_t mTime, @@ -620,18 +535,14 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* path, uint32_t linkMinOSVersion, bool allowSimToMacOSX, bool addVers, bool buildingForSimulator, bool logAllFiles, const char* targetInstallPath, bool indirectDylib) - : ld::dylib::File(strdup(path), mTime, ord), _platform(platform), _cpuType(cpuType), - _linkMinOSVersion(linkMinOSVersion), _allowSimToMacOSXLinking(allowSimToMacOSX), - _addVersionLoadCommand(addVers), _linkingFlat(linkingFlatNamespace), - _implicitlyLinkPublicDylibs(hoistImplicitPublicDylibs), - _objcConstraint(ld::File::objcConstraintNone), _swiftVersion(0), - _importProxySection("__TEXT", "__import", ld::Section::typeImportProxies, true), - _flatDummySection("__LINKEDIT", "__flat_dummy", ld::Section::typeLinkEdit, true), - _noRexports(false), _hasWeakExports(false), - _hasPublicInstallName(false), _providedAtom(false), _wrongOS(false), - _installPathOverride(false), _indirectDylibsProcessed(false), - _bitcode(new ld::Bitcode(nullptr, 0)) + : Base(strdup(path), mTime, ord, platform, linkMinOSVersion, linkingFlatNamespace, + hoistImplicitPublicDylibs, allowSimToMacOSX, addVers), + _cpuType(cpuType) { + this->_bitcode = std::unique_ptr(new ld::Bitcode(nullptr, 0)); + // Text stubs are implicit app extension safe. + this->_appExtensionSafe = true; + // write out path for -t option if ( logAllFiles ) printf("%s\n", path); @@ -639,27 +550,66 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* path, TBDFile stub((const char*)fileContent, fileLength); auto lib = stub.parseFileForArch(archName); - _noRexports = lib._reexportedLibraries.empty(); - _hasWeakExports = !lib._weakDefSymbols.empty(); - _dylibInstallPath = strdup(lib._installName.str().c_str()); - _dylibCurrentVersion = lib._currentVersion; - _dylibCompatibilityVersion = lib._compatibilityVersion; - _swiftVersion = lib._swiftVersion; - _objcConstraint = lib._objcConstraint; - _hasPublicInstallName = isPublicLocation(_dylibInstallPath); - - for (auto &client : lib._allowedClients) - _allowableClients.push_back(strdup(client.str().c_str())); + this->_noRexports = lib._reexportedLibraries.empty(); + this->_hasWeakExports = !lib._weakDefSymbols.empty(); + this->_dylibInstallPath = strdup(lib._installName.str().c_str()); + this->_dylibCurrentVersion = lib._currentVersion; + this->_dylibCompatibilityVersion = lib._compatibilityVersion; + this->_swiftVersion = lib._swiftVersion; + this->_objcConstraint = lib._objcConstraint; + this->_hasPublicInstallName = this->isPublicLocation(this->_dylibInstallPath); + + // if framework, capture framework name + const char* lastSlash = strrchr(this->_dylibInstallPath, '/'); + if ( lastSlash != NULL ) { + const char* leafName = lastSlash+1; + char frname[strlen(leafName)+32]; + strcpy(frname, leafName); + strcat(frname, ".framework/"); + + if ( strstr(this->_dylibInstallPath, frname) != NULL ) + this->_frameworkName = leafName; + } + + // TEMPORARY HACK BEGIN: Support ancient re-export command LC_SUB_FRAMEWORK. + // [TAPI] Support LC_SUB_FRAMEWORK as re-export indicator. + auto installName = std::string(this->_dylibInstallPath); + + // All sub-frameworks of ApplicationServices use LC_SUB_FRAMEWORK. + if (installName.find("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/") == 0 && + installName.find(".dylib") == std::string::npos) { + this->_parentUmbrella = "ApplicationServices"; + } else if (installName.find("/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/") == 0) { + this->_parentUmbrella = "Carbon"; + } else if (installName.find("/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/") == 0 && + installName.find(".dylib") == std::string::npos) { + this->_parentUmbrella = "CoreServices"; + } else if (installName.find("/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLinearAlgebra.dylib") == 0 || + installName.find("/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libQuadrature.dylib") == 0 || + installName.find("System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparseBLAS.dylib") == 0) { + this->_parentUmbrella = "vecLib"; + } else if (installName.find("/System/Library/Frameworks/WebKit.framework/Versions/A/Frameworks/WebCore.framework/Versions/A/WebCore") == 0) { + this->_parentUmbrella = "WebKit"; + } else if (installName.find("/usr/lib/system/") == 0 && + installName != "/usr/lib/system/libkxld.dylib") { + this->_parentUmbrella = "System"; + } + // TEMPORARY HACK END + + for (auto &client : lib._allowedClients) { + if ((this->_parentUmbrella != nullptr) && (client.str() != this->_parentUmbrella)) + this->_allowableClients.push_back(strdup(client.str().c_str())); + } // [TAPI] Don't hoist "public" (in /usr/lib/) dylibs that should not be directly linked - if ( !_allowableClients.empty() ) - _hasPublicInstallName = false; + if ( !this->_allowableClients.empty() ) + this->_hasPublicInstallName = false; if ( (lib._platform != platform) && (platform != Options::kPlatformUnknown) ) { - _wrongOS = true; - if ( _addVersionLoadCommand && !indirectDylib ) { + this->_wrongOS = true; + if ( this->_addVersionLoadCommand && !indirectDylib ) { if ( buildingForSimulator ) { - if ( !_allowSimToMacOSXLinking ) + if ( !this->_allowSimToMacOSXLinking ) throwf("building for %s simulator, but linking against dylib built for %s (%s).", Options::platformName(platform), Options::platformName(lib._platform), path); } else { @@ -669,13 +619,11 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* path, } } - _dependentDylibs.reserve(lib._reexportedLibraries.size()); - for ( auto& reexport : lib._reexportedLibraries ) { - Dependent entry; - entry.path = strdup(reexport.str().c_str()); - entry.dylib = nullptr; - if ( (targetInstallPath == nullptr) || (strcmp(targetInstallPath, entry.path) != 0) ) - _dependentDylibs.push_back(entry); + this->_dependentDylibs.reserve(lib._reexportedLibraries.size()); + for ( const auto& reexport : lib._reexportedLibraries ) { + const char *path = strdup(reexport.str().c_str()); + if ( (targetInstallPath == nullptr) || (strcmp(targetInstallPath, path) != 0) ) + this->_dependentDylibs.emplace_back(path, true); } // build hash table @@ -686,324 +634,51 @@ File::File(const uint8_t* fileContent, uint64_t fileLength, const char* path, template void File::buildExportHashTable(const DynamicLibrary& lib) { - if ( _s_logHashtable ) + if (this->_s_logHashtable ) fprintf(stderr, "ld: building hashtable from text-stub info in %s\n", this->path()); for (auto &sym : lib._symbols) - addSymbol(sym.str().c_str(), /*weak=*/false, /*tlv=*/false); + this->addSymbol(sym.str().c_str()); #if SUPPORT_ARCH_i386 - if (_platform == Options::kPlatformOSX && _cpuType == CPU_TYPE_I386) { + if (this->_platform == Options::kPlatformOSX && _cpuType == CPU_TYPE_I386) { for (auto &sym : lib._classes) - addSymbol((".objc_class_name" + sym.str()).c_str(), /*weak=*/false, /*tlv=*/false); + this->addSymbol((".objc_class_name" + sym.str()).c_str()); } else { for (auto &sym : lib._classes) { - addSymbol(("_OBJC_CLASS_$" + sym.str()).c_str(), /*weak=*/false, /*tlv=*/false); - addSymbol(("_OBJC_METACLASS_$" + sym.str()).c_str(), /*weak=*/false, /*tlv=*/false); + this->addSymbol(("_OBJC_CLASS_$" + sym.str()).c_str()); + this->addSymbol(("_OBJC_METACLASS_$" + sym.str()).c_str()); } } #else for (auto &sym : lib._classes) { - addSymbol(("_OBJC_CLASS_$" + sym.str()).c_str(), /*weak=*/false, /*tlv=*/false); - addSymbol(("_OBJC_METACLASS_$" + sym.str()).c_str(), /*weak=*/false, /*tlv=*/false); + this->addSymbol(("_OBJC_CLASS_$" + sym.str()).c_str()); + this->addSymbol(("_OBJC_METACLASS_$" + sym.str()).c_str()); } #endif for (auto &sym : lib._ivars) - addSymbol(("_OBJC_IVAR_$" + sym.str()).c_str(), /*weak=*/false, /*tlv=*/false); + this->addSymbol(("_OBJC_IVAR_$" + sym.str()).c_str()); for (auto &sym : lib._weakDefSymbols) - addSymbol(sym.str().c_str(), /*weak=*/true, /*tlv=*/false); + this->addSymbol(sym.str().c_str(), /*weak=*/true); for (auto &sym : lib._tlvSymbols) - addSymbol(sym.str().c_str(), /*weak=*/false, /*tlv=*/true); -} - - -template -void File::addSymbol(const char* name, bool weakDef, bool tlv) -{ - // symbols that start with $ld$ are meta-data to the static linker - // need way for ld and dyld to see different exported symbols in a dylib - if ( strncmp(name, "$ld$", 4) == 0 ) { - // $ld$ $ $ - const char* symAction = &name[4]; - const char* symCond = strchr(symAction, '$'); - if ( symCond != nullptr ) { - char curOSVers[16]; - sprintf(curOSVers, "$os%d.%d$", (_linkMinOSVersion >> 16), ((_linkMinOSVersion >> 8) & 0xFF)); - if ( strncmp(symCond, curOSVers, strlen(curOSVers)) == 0 ) { - const char* symName = strchr(&symCond[1], '$'); - if ( symName != nullptr ) { - ++symName; - if ( strncmp(symAction, "hide$", 5) == 0 ) { - if ( _s_logHashtable ) - fprintf(stderr, " adding %s to ignore set for %s\n", symName, this->path()); - _ignoreExports.insert(strdup(symName)); - return; - } - else if ( strncmp(symAction, "add$", 4) == 0 ) { - this->addSymbol(symName, weakDef, false); - return; - } - else if ( strncmp(symAction, "install_name$", 13) == 0 ) { - _dylibInstallPath = strdup(symName); - _installPathOverride = true; - return; - } - else if ( strncmp(symAction, "compatibility_version$", 22) == 0 ) { - _dylibCompatibilityVersion = parseVersionNumber32(symName); - return; - } - else { - warning("bad symbol action: %s in dylib %s", name, this->path()); - } - } - } - } - else { - warning("bad symbol condition: %s in dylib %s", name, this->path()); - } - } - - // add symbol as possible export if we are not supposed to ignore it - if ( _ignoreExports.count(name) == 0 ) { - AtomAndWeak bucket; - bucket.atom = nullptr; - bucket.weakDef = weakDef; - bucket.tlv = tlv; - if ( _s_logHashtable ) - fprintf(stderr, " adding %s to hash table for %s\n", name, this->path()); - _atoms[strdup(name)] = bucket; - } -} - - -template -bool File::forEachAtom(ld::File::AtomHandler& handler) const -{ - handler.doFile(*this); - return false; + this->addSymbol(sym.str().c_str(), /*weak=*/false, /*tlv=*/true); } - -template -std::pair File::hasWeakDefinitionImpl(const char* name) const -{ - const auto pos = _atoms.find(name); - if ( pos != _atoms.end() ) - return std::make_pair(true, pos->second.weakDef); - - // look in children that I re-export - for (const auto &dep : _dependentDylibs) { - auto ret = dep.dylib->hasWeakDefinitionImpl(name); - if ( ret.first ) - return ret; - } - return std::make_pair(false, false); -} - - -template -bool File::hasWeakDefinition(const char* name) const -{ - // if supposed to ignore this export, then pretend I don't have it - if ( _ignoreExports.count(name) != 0 ) - return false; - - return hasWeakDefinitionImpl(name).second; -} - - -// If only weak_import symbols are used, linker should use LD_LOAD_WEAK_DYLIB -template -bool File::allSymbolsAreWeakImported() const -{ - bool foundNonWeakImport = false; - bool foundWeakImport = false; - for (const auto &it : _atoms) { - const ld::Atom* atom = it.second.atom; - if ( atom != nullptr ) { - if ( atom->weakImported() ) - foundWeakImport = true; - else - foundNonWeakImport = true; - } - } - - // don't automatically weak link dylib with no imports - // so at least one weak import symbol and no non-weak-imported symbols must be found - return foundWeakImport && !foundNonWeakImport; -} - - -template -bool File::containsOrReExports(const char* name, bool& weakDef, bool& tlv, uint64_t& addr) const -{ - if ( _ignoreExports.count(name) != 0 ) - return false; - - // check myself - const auto pos = _atoms.find(name); - if ( pos != _atoms.end() ) { - weakDef = pos->second.weakDef; - tlv = pos->second.tlv; - addr = 0; - return true; - } - - // check dylibs I re-export - for (const auto& lib : _dependentDylibs) { - if ( !lib.dylib->implicitlyLinked() ) { - if ( lib.dylib->containsOrReExports(name, weakDef, tlv, addr) ) - return true; - } - } - - return false; -} - - -template -bool File::justInTimeforEachAtom(const char* name, ld::File::AtomHandler& handler) const -{ - // if supposed to ignore this export, then pretend I don't have it - if ( _ignoreExports.count(name) != 0 ) - return false; - - - AtomAndWeak bucket; - uint64_t addr; - if ( this->containsOrReExports(name, bucket.weakDef, bucket.tlv, addr) ) { - bucket.atom = new ExportAtom(*this, name, bucket.weakDef, bucket.tlv); - _atoms[name] = bucket; - _providedAtom = true; - if ( _s_logHashtable ) - fprintf(stderr, "getJustInTimeAtomsFor: %s found in %s\n", name, this->path()); - // call handler with new export atom - handler.doAtom(*bucket.atom); - return true; - } - - return false; -} - - - -template -bool File::isPublicLocation(const char* path) -{ - // -no_implicit_dylibs disables this optimization - if ( ! _implicitlyLinkPublicDylibs ) - return false; - - // /usr/lib is a public location - if ( (strncmp(path, "/usr/lib/", 9) == 0) && (strchr(&path[9], '/') == nullptr) ) - return true; - - // /System/Library/Frameworks/ is a public location - if ( strncmp(path, "/System/Library/Frameworks/", 27) == 0 ) { - const char* frameworkDot = strchr(&path[27], '.'); - // but only top level framework - // /System/Library/Frameworks/Foo.framework/Versions/A/Foo ==> true - // /System/Library/Frameworks/Foo.framework/Resources/libBar.dylib ==> false - // /System/Library/Frameworks/Foo.framework/Frameworks/Bar.framework/Bar ==> false - // /System/Library/Frameworks/Foo.framework/Frameworks/Xfoo.framework/XFoo ==> false - if ( frameworkDot != nullptr ) { - int frameworkNameLen = frameworkDot - &path[27]; - if ( strncmp(&path[strlen(path)-frameworkNameLen-1], &path[26], frameworkNameLen+1) == 0 ) - return true; - } - } - - return false; -} - -template -void File::processIndirectLibraries(ld::dylib::File::DylibHandler* handler, bool addImplicitDylibs) -{ - // only do this once - if ( _indirectDylibsProcessed ) - return; - - const static bool log = false; - if ( log ) fprintf(stderr, "processIndirectLibraries(%s)\n", this->installPath()); - if ( _linkingFlat ) { - for (auto& lib : _dependentDylibs) { - lib.dylib = (File*)handler->findDylib(lib.path, this->path()); - } - } - else if ( _noRexports ) { - // MH_NO_REEXPORTED_DYLIBS bit set, then nothing to do - } - else { - // two-level, might have re-exports - for (auto& lib : _dependentDylibs) { - if ( log ) - fprintf(stderr, "processIndirectLibraries() parent=%s, child=%s\n", this->installPath(), lib.path); - // a LC_REEXPORT_DYLIB, LC_SUB_UMBRELLA or LC_SUB_LIBRARY says we re-export this child - lib.dylib = (File*)handler->findDylib(lib.path, this->path()); - if ( lib.dylib->hasPublicInstallName() && !lib.dylib->wrongOS() ) { - // promote this child to be automatically added as a direct dependent if this already is - if ( (this->explicitlyLinked() || this->implicitlyLinked()) && (strcmp(lib.path, lib.dylib->installPath()) == 0) ) { - if ( log ) - fprintf(stderr, "processIndirectLibraries() implicitly linking %s\n", lib.dylib->installPath()); - lib.dylib->setImplicitlyLinked(); - } - else if ( lib.dylib->explicitlyLinked() || lib.dylib->implicitlyLinked() ) { - if ( log ) - fprintf(stderr, "processIndirectLibraries() parent is not directly linked, but child is, so no need to re-export child\n"); - } else { - if ( log ) - fprintf(stderr, "processIndirectLibraries() parent is not directly linked, so parent=%s will re-export child=%s\n", this->installPath(), lib.path); - } - } else { - // add all child's symbols to me - if ( log ) - fprintf(stderr, "processIndirectLibraries() child is not public, so parent=%s will re-export child=%s\n", this->installPath(), lib.path); - } - } - } - - // check for re-export cycles - ReExportChain chain; - chain.prev = nullptr; - chain.file = this; - this->assertNoReExportCycles(&chain); - - _indirectDylibsProcessed = true; -} - -template -void File::assertNoReExportCycles(ReExportChain* prev) const -{ - // recursively check my re-exported dylibs - ReExportChain chain; - chain.prev = prev; - chain.file = this; - for (const auto& dep : _dependentDylibs) { - ld::File* child = dep.dylib; - // check child is not already in chain - for (ReExportChain* p = prev; p != nullptr; p = p->prev) { - if ( p->file == child ) - throwf("cycle in dylib re-exports with %s and %s", child->path(), this->path()); - } - if ( dep.dylib != nullptr ) - dep.dylib->assertNoReExportCycles(&chain); - } -} - - template class Parser { public: - typedef typename A::P P; + using P = typename A::P; - static bool validFile(const uint8_t* fileContent, uint64_t fileLength, const std::string &path, const char* archName); + static bool validFile(const uint8_t* fileContent, uint64_t fileLength, + const std::string &path, const char* archName); static ld::dylib::File* parse(const uint8_t* fileContent, uint64_t fileLength, const char* path, time_t mTime, ld::File::Ordinal ordinal, const Options& opts, - bool indirectDylib) { + bool indirectDylib) + { return new File(fileContent, fileLength, path, mTime, ordinal, opts.flatNamespace(), opts.implicitlyLinkIndirectPublicDylibs(), @@ -1021,7 +696,8 @@ public: }; template -bool Parser::validFile(const uint8_t* fileContent, uint64_t fileLength, const std::string &path, const char* archName) +bool Parser::validFile(const uint8_t* fileContent, uint64_t fileLength, const std::string &path, + const char* archName) { if ( path.find(".tbd", path.size()-4) == std::string::npos ) return false; @@ -1072,5 +748,3 @@ ld::dylib::File* parse(const uint8_t* fileContent, uint64_t fileLength, const ch } // namespace dylib } // namespace textstub - - diff --git a/src/ld/passes/bitcode_bundle.cpp b/src/ld/passes/bitcode_bundle.cpp index 2ad5a3e..dce3783 100644 --- a/src/ld/passes/bitcode_bundle.cpp +++ b/src/ld/passes/bitcode_bundle.cpp @@ -268,6 +268,9 @@ BitcodeTempFile::~BitcodeTempFile() BitcodeObfuscator::BitcodeObfuscator() { +#if LTO_API_VERSION < 11 + throwf("compile-time libLTO (%d) didn't support -bitcode_hide_symbols", LTO_API_VERSION); +#else // check if apple internal libLTO is used if ( ::lto_get_version() == NULL ) throwf("libLTO is not loaded"); @@ -282,11 +285,13 @@ BitcodeObfuscator::BitcodeObfuscator() _lto_get_asm_symbol_num == NULL || _lto_get_asm_symbol_name == NULL || ::lto_api_version() < 14 ) throwf("loaded libLTO doesn't support -bitcode_hide_symbols: %s", ::lto_get_version()); _obfuscator = ::lto_codegen_create_in_local_context(); -#if LTO_API_VERSION >= 14 + #if LTO_API_VERSION >= 14 lto_codegen_set_should_internalize(_obfuscator, false); + #endif #endif } + BitcodeObfuscator::~BitcodeObfuscator() { ::lto_codegen_dispose(_obfuscator); @@ -302,7 +307,8 @@ void BitcodeObfuscator::bitcodeHideSymbols(ld::Bitcode* bc, const char* filePath #if LTO_API_VERSION >= 13 && LTO_APPLE_INTERNAL lto_module_t module = ::lto_module_create_in_codegen_context(bc->getContent(), bc->getSize(), filePath, _obfuscator); if ( module == NULL ) - throwf("object contains invalid bitcode: %s", filePath); + throwf("could not reparse object file %s in bitcode bundle: '%s', using libLTO version '%s'", + filePath, ::lto_get_error_message(), ::lto_get_version()); ::lto_codegen_set_module(_obfuscator, module); (*_lto_hide_symbols)(_obfuscator); #if LTO_API_VERSION >= 15 @@ -397,6 +403,8 @@ void BundleHandler::init() // read the xar file _xar = xar_open(oldXARPath.c_str(), READ); + if ( _xar == NULL ) + throwf("malformed bundle format"); // Init the vector of handler xar_iter_t iter = xar_iter_new(); @@ -431,8 +439,10 @@ void BundleHandler::copyXARProp(xar_file_t src, xar_file_t dst) const char* key = xar_prop_first(src, p); for (int x = 0; x < i; x++) key = xar_prop_next(p); - if ( !key ) + if ( !key ) { + xar_iter_free(p); break; + } const char* val = NULL; xar_prop_get(src, key, &val); if ( // Info from bitcode files @@ -471,7 +481,14 @@ void BitcodeHandler::populateMustPreserveSymbols(BitcodeObfuscator* obfuscator) initFile(); // init LTOModule and add asm labels +#if LTO_API_VERSION < 11 lto_module_t module = lto_module_create_from_memory(_file_buffer, _file_size); +#else + lto_module_t module = lto_module_create_in_local_context(_file_buffer, _file_size, "bitcode bundle temp file"); +#endif + if ( module == NULL ) + throwf("could not reparse object file in bitcode bundle: '%s', using libLTO version '%s'", + ::lto_get_error_message(), ::lto_get_version()); obfuscator->addAsmSymbolsToMustPreserve(module); lto_module_dispose(module); } @@ -617,14 +634,16 @@ void BitcodeBundle::doPass() // 3. symbols must not be stripped // 4. all the globals if the globals are dead_strip root (ex. dylibs) // 5. there is an exported symbol list suggests the symbol should be exported - // 6. the special symbols supplied by linker + // 6. weak external symbols (not auto-hide) + // 7. the special symbols supplied by linker for ( auto § : _state.sections ) { for ( auto &atom : sect->atoms ) { if ( atom == _state.entryPoint || atom->definition() == ld::Atom::definitionProxy || atom->symbolTableInclusion() == ld::Atom::symbolTableInAndNeverStrip || ( _options.allGlobalsAreDeadStripRoots() && atom->scope() == ld::Atom::scopeGlobal ) || - ( _options.hasExportRestrictList() && _options.shouldExport(atom->name()) ) ) + ( _options.hasExportRestrictList() && _options.shouldExport(atom->name()) ) || + ( atom->combine() == ld::Atom::combineByName && atom->scope() == ld::Atom::scopeGlobal && !atom->autoHide() ) ) obfuscator->addMustPreserveSymbols(atom->name()); } } @@ -650,6 +669,11 @@ void BitcodeBundle::doPass() obfuscator->addMustPreserveSymbols("__mh_dylinker_header"); obfuscator->addMustPreserveSymbols("__mh_object_header"); obfuscator->addMustPreserveSymbols("__mh_preload_header"); + + // add all the Proxy Atom linker ever created and all the undefs that are possibily dead-stripped. + for (auto sym : _state.allUndefProxies) + obfuscator->addMustPreserveSymbols(sym); + _state.allUndefProxies.clear(); } // Open XAR output diff --git a/src/ld/passes/branch_island.cpp b/src/ld/passes/branch_island.cpp index f7465d0..65e3cf2 100644 --- a/src/ld/passes/branch_island.cpp +++ b/src/ld/passes/branch_island.cpp @@ -389,7 +389,7 @@ static uint64_t maxDistanceBetweenIslands(const Options& opts, bool seenThumbBra // -static void makeIslandsForSection(const Options& opts, ld::Internal& state, ld::Internal::FinalSection* textSection) +static void makeIslandsForSection(const Options& opts, ld::Internal& state, ld::Internal::FinalSection* textSection, unsigned stubCount) { // assign section offsets to each atom in __text section, watch for thumb branches, and find total size bool hasThumbBranches = false; @@ -448,7 +448,7 @@ static void makeIslandsForSection(const Options& opts, ld::Internal& state, ld:: (const_cast(atom))->setSectionOffset(offset); offset += atom->size(); } - uint64_t totalTextSize = offset; + uint64_t totalTextSize = offset + stubCount*16; if ( (totalTextSize < textSizeWhenMightNeedBranchIslands(opts, hasThumbBranches)) && !haveCrossSectionBranches ) return; if (_s_log) fprintf(stderr, "ld: section %s size=%llu, might need branch islands\n", textSection->sectionName(), totalTextSize); @@ -723,11 +723,19 @@ void doPass(const Options& opts, ld::Internal& state) buildAddressMap(opts, state); } + // scan sections for number of stubs + unsigned stubCount = 0; + for (std::vector::iterator sit=state.sections.begin(); sit != state.sections.end(); ++sit) { + ld::Internal::FinalSection* sect = *sit; + if ( sect->type() == ld::Section::typeStub ) + stubCount = sect->atoms.size(); + } + // scan sections and add island to each code section for (std::vector::iterator sit=state.sections.begin(); sit != state.sections.end(); ++sit) { ld::Internal::FinalSection* sect = *sit; if ( sect->type() == ld::Section::typeCode ) - makeIslandsForSection(opts, state, sect); + makeIslandsForSection(opts, state, sect, stubCount); } } diff --git a/src/ld/passes/branch_shim.cpp b/src/ld/passes/branch_shim.cpp index efc010c..7be33d2 100644 --- a/src/ld/passes/branch_shim.cpp +++ b/src/ld/passes/branch_shim.cpp @@ -266,7 +266,7 @@ static void extractTarget(ld::Fixup::iterator fixup, ld::Internal& state, const // -// The tail-call optimzation may result in a function ending in a jump (b) +// The tail-call optimization may result in a function ending in a jump (b) // to another functions. At compile time the compiler does not know // if the target of the jump will be in the same mode (arm vs thumb). // The arm/thumb instruction set has a way to change modes in a bl(x) diff --git a/src/ld/passes/code_dedup.cpp b/src/ld/passes/code_dedup.cpp new file mode 100644 index 0000000..e067acf --- /dev/null +++ b/src/ld/passes/code_dedup.cpp @@ -0,0 +1,375 @@ +/* -*- 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@ + */ + + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ld.hpp" +#include "code_dedup.h" + +namespace ld { +namespace passes { +namespace dedup { + + + +class DeDupAliasAtom : public ld::Atom +{ +public: + DeDupAliasAtom(const ld::Atom* dupOf, const ld::Atom* replacement) : + ld::Atom(dupOf->section(), ld::Atom::definitionRegular, ld::Atom::combineNever, + dupOf->scope(), dupOf->contentType(), ld::Atom::symbolTableIn, + false, false, true, 0), + _dedupOf(dupOf), + _fixup(0, ld::Fixup::k1of1, ld::Fixup::kindNoneFollowOn, ld::Fixup::bindingDirectlyBound, replacement) { + if ( dupOf->autoHide() ) + setAutoHide(); + } + + virtual const ld::File* file() const { return _dedupOf->file(); } + virtual const char* translationUnitSource() const + { return NULL; } + virtual const char* name() const { return _dedupOf->name(); } + virtual uint64_t size() const { return 0; } + virtual uint64_t objectAddress() const { return 0; } + virtual void copyRawContent(uint8_t buffer[]) const { } + virtual ld::Fixup::iterator fixupsBegin() const { return &((ld::Fixup*)&_fixup)[0]; } + virtual ld::Fixup::iterator fixupsEnd() const { return &((ld::Fixup*)&_fixup)[1]; } + +private: + const ld::Atom* _dedupOf; + ld::Fixup _fixup; +}; + + +namespace { + typedef std::unordered_map CachedHashes; + + ld::Internal* sState = nullptr; + CachedHashes sSavedHashes; + unsigned long sHashCount = 0; + unsigned long sFixupCompareCount = 0; +}; + + +// A helper for std::unordered_map<> that hashes the instructions of a function +struct atom_hashing { + + static unsigned long hash(const ld::Atom* atom) { + auto pos = sSavedHashes.find(atom); + if ( pos != sSavedHashes.end() ) + return pos->second; + + const unsigned instructionBytes = atom->size(); + const uint8_t* instructions = atom->rawContentPointer(); + unsigned long hash = instructionBytes; + for (unsigned i=0; i < instructionBytes; ++i) { + hash = (hash * 33) + instructions[i]; + } + for (ld::Fixup::iterator fit = atom->fixupsBegin(), end=atom->fixupsEnd(); fit != end; ++fit) { + const Atom* target = NULL; + switch ( fit->binding ) { + case ld::Fixup::bindingDirectlyBound: + target = fit->u.target; + break; + case ld::Fixup::bindingsIndirectlyBound: + target = sState->indirectBindingTable[fit->u.bindingIndex]; + break; + default: + break; + } + // don't include calls to auto-hide functions in hash because they might be de-dup'ed + switch ( fit->kind ) { +#if SUPPORT_ARCH_arm64 + case ld::Fixup::kindStoreTargetAddressARM64Branch26: +#endif + case ld::Fixup::kindStoreTargetAddressX86BranchPCRel32: + if ( target && target->autoHide() ) + continue; // don't include + break; + default: + break; + } + if ( target != NULL ) { + const char* name = target->name(); + if ( target->contentType() == ld::Atom::typeCString ) + name = (const char*)target->rawContentPointer(); + for (const char* s = name; *s != '\0'; ++s) + hash = (hash * 33) + *s; + } + } + ++sHashCount; + sSavedHashes[atom] = hash; + return hash; + } + + unsigned long operator()(const ld::Atom* atom) const { + return hash(atom); + } +}; + + +// A helper for std::unordered_map<> that compares functions +struct atom_equal { + + struct VisitedSet { + std::unordered_set atoms1; + std::unordered_set atoms2; + }; + + static bool sameFixups(const ld::Atom* atom1, const ld::Atom* atom2, VisitedSet& visited) { + ++sFixupCompareCount; + //fprintf(stderr, "sameFixups(%s,%s)\n", atom1->name(), atom2->name()); + Fixup::iterator f1 = atom1->fixupsBegin(); + Fixup::iterator end1 = atom1->fixupsEnd(); + Fixup::iterator f2 = atom2->fixupsBegin(); + Fixup::iterator end2 = atom2->fixupsEnd(); + // two atoms must have same number of fixups + if ( (end1 - f1) != (end2 - f2) ) + return false; + // if no fixups, fixups are equal + if ( f1 == end1 ) + return true; + // all fixups must be the same + do { + if ( f1->offsetInAtom != f2->offsetInAtom ) + return false; + if ( f1->kind != f2->kind ) + return false; + if ( f1->clusterSize != f2->clusterSize ) + return false; + if ( f1->binding != f2->binding ) + return false; + const Atom* target1 = NULL; + const Atom* target2 = NULL; + switch ( f1->binding ) { + case ld::Fixup::bindingDirectlyBound: + target1 = f1->u.target; + target2 = f2->u.target; + break; + case ld::Fixup::bindingsIndirectlyBound: + target1 = sState->indirectBindingTable[f1->u.bindingIndex]; + target2 = sState->indirectBindingTable[f2->u.bindingIndex]; + break; + case ld::Fixup::bindingNone: + break; + default: + return false; + } + if ( target1 != target2 ) { + // targets must match unless they are both calls to functions that will de-dup together + #if SUPPORT_ARCH_arm64 + if ( (f1->kind != ld::Fixup::kindStoreTargetAddressARM64Branch26) && (f1->kind != ld::Fixup::kindStoreTargetAddressX86BranchPCRel32) ) + #else + if ( f1->kind != ld::Fixup::kindStoreTargetAddressX86BranchPCRel32 ) + #endif + return false; + if ( target1->section().type() != target2->section().type() ) + return false; + if ( target1->section().type() != ld::Section::typeCode ) + return false; + // to support co-recursive functions, don't call equals() on targets we've already visited + if ( ((visited.atoms1.count(target1) == 0) || (visited.atoms2.count(target2) == 0)) && !equal(target1, target2, visited) ) + return false; + } + + ++f1; + ++f2; + } while (f1 != end1); + + return true; + } + + static bool equal(const ld::Atom* atom1, const ld::Atom* atom2, VisitedSet& visited) { + if ( atom_hashing::hash(atom1) != atom_hashing::hash(atom2) ) + return false; + visited.atoms1.insert(atom1); + visited.atoms2.insert(atom2); + return sameFixups(atom1, atom2, visited); + } + + bool operator()(const ld::Atom* atom1, const ld::Atom* atom2) const { + VisitedSet visited; + return equal(atom1, atom2, visited); + } +}; + + + +void doPass(const Options& opts, ld::Internal& state) +{ + const bool log = false; + + // only de-duplicate in final linked images + if ( opts.outputKind() == Options::kObjectFile ) + return; + + // only de-duplicate for architectures that use relocations that don't store bits in instructions + if ( (opts.architecture() != CPU_TYPE_ARM64) && (opts.architecture() != CPU_TYPE_X86_64) ) + return; + + // support -no_deduplicate to suppress this pass + if ( ! opts.deduplicateFunctions() ) + return; + + const bool verbose = opts.verboseDeduplicate(); + + // find __text section + ld::Internal::FinalSection* textSection = NULL; + for (ld::Internal::FinalSection* sect : state.sections) { + if ( (sect->type() == ld::Section::typeCode) && (strcmp(sect->sectionName(), "__text") == 0) ) { + textSection = sect; + break; + } + } + if ( textSection == NULL ) + return; + + // build map of auto-hide functions and their duplicates + // the key for the map is always the first element in the value vector + // the key is always earlier in the atoms list then matching other atoms + sState = &state; + std::unordered_map, atom_hashing, atom_equal> map; + std::unordered_set masterAtoms; + for (const ld::Atom* atom : textSection->atoms) { + // ignore empty (alias) atoms + if ( atom->size() == 0 ) + continue; + if ( atom->autoHide() ) + map[atom].push_back(atom); + } + + if ( log ) { + for (auto& entry : map) { + if ( entry.second.size() > 1 ) { + printf("Found following matching functions:\n"); + for (const ld::Atom* atom : entry.second) { + printf(" %p %s\n", atom, atom->name()); + } + } + } + fprintf(stderr, "duplicate sets count:\n"); + for (auto& entry : map) + fprintf(stderr, " %p -> %lu\n", entry.first, entry.second.size()); + } + + // construct alias atoms to replace atoms found to be duplicates + unsigned atomsBeingComparedCount = 0; + uint64_t dedupSavings = 0; + std::vector& textAtoms = textSection->atoms; + std::unordered_map replacementMap; + for (auto& entry : map) { + const ld::Atom* masterAtom = entry.first; + std::vector& dups = entry.second; + atomsBeingComparedCount += dups.size(); + if ( dups.size() == 1 ) + continue; + if ( verbose ) { + dedupSavings += ((dups.size() - 1) * masterAtom->size()); + fprintf(stderr, "deduplicate the following %lu functions (%llu bytes apiece):\n", dups.size(), masterAtom->size()); + } + for (const ld::Atom* dupAtom : dups) { + if ( verbose ) + fprintf(stderr, " %s\n", dupAtom->name()); + if ( dupAtom == masterAtom ) + continue; + const ld::Atom* aliasAtom = new DeDupAliasAtom(dupAtom, masterAtom); + auto pos = std::find(textAtoms.begin(), textAtoms.end(), masterAtom); + if ( pos != textAtoms.end() ) { + textAtoms.insert(pos, aliasAtom); + state.atomToSection[aliasAtom] = textSection; + replacementMap[dupAtom] = aliasAtom; + } + } + } + if ( verbose ) { + fprintf(stderr, "deduplication saved %llu bytes of __text\n", dedupSavings); + } + + if ( log ) { + fprintf(stderr, "replacement map:\n"); + for (auto& entry : replacementMap) + fprintf(stderr, " %p -> %p\n", entry.first, entry.second); + } + + // walk all atoms and replace references to dups with references to alias + for (ld::Internal::FinalSection* sect : state.sections) { + for (const ld::Atom* atom : sect->atoms) { + for (ld::Fixup::iterator fit = atom->fixupsBegin(), end=atom->fixupsEnd(); fit != end; ++fit) { + std::unordered_map::iterator pos; + switch ( fit->binding ) { + case ld::Fixup::bindingsIndirectlyBound: + pos = replacementMap.find(state.indirectBindingTable[fit->u.bindingIndex]); + if ( pos != replacementMap.end() ) + state.indirectBindingTable[fit->u.bindingIndex] = pos->second; + break; + case ld::Fixup::bindingDirectlyBound: + pos = replacementMap.find(fit->u.target); + if ( pos != replacementMap.end() ) + fit->u.target = pos->second; + break; + default: + break; + } + } + } + } + + if ( log ) { + fprintf(stderr, "atoms before pruning:\n"); + for (const ld::Atom* atom : textSection->atoms) + fprintf(stderr, " %p (size=%llu) %sp\n", atom, atom->size(), atom->name()); + } + // remove replaced atoms from section + textSection->atoms.erase(std::remove_if(textSection->atoms.begin(), textSection->atoms.end(), + [&](const ld::Atom* atom) { + return (replacementMap.count(atom) != 0); + }), + textSection->atoms.end()); + + for (auto& entry : replacementMap) + state.atomToSection.erase(entry.first); + + if ( log ) { + fprintf(stderr, "atoms after pruning:\n"); + for (const ld::Atom* atom : textSection->atoms) + fprintf(stderr, " %p (size=%llu) %sp\n", atom, atom->size(), atom->name()); + } + + //fprintf(stderr, "hash-count=%lu, fixup-compares=%lu, atom-count=%u\n", sHashCount, sFixupCompareCount, atomsBeingComparedCount); +} + + +} // namespace dedup +} // namespace passes +} // namespace ld diff --git a/src/ld/passes/code_dedup.h b/src/ld/passes/code_dedup.h new file mode 100644 index 0000000..0b803ba --- /dev/null +++ b/src/ld/passes/code_dedup.h @@ -0,0 +1,45 @@ +/* -*- 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 __DEDUPE_H__ +#define __DEDUPE_H__ + +#include "Options.h" +#include "ld.hpp" + + +namespace ld { +namespace passes { +namespace dedup { + +// called by linker to merge duplicate functions +extern void doPass(const Options& opts, ld::Internal& internal); + + +} // namespace huge +} // namespace passes +} // namespace ld + +#endif // __DEDUPE_H__ diff --git a/src/ld/passes/compact_unwind.cpp b/src/ld/passes/compact_unwind.cpp index 1d257ee..d2847bb 100644 --- a/src/ld/passes/compact_unwind.cpp +++ b/src/ld/passes/compact_unwind.cpp @@ -630,11 +630,10 @@ unsigned int UnwindInfoAtom::makeCompressedSecondLevelPage(const std::vector< // keep adding entries to page until: // 1) encoding table plus entry table plus header exceed page size // 2) the file offset delta from the first to last function > 24 bits - // 3) custom encoding index reachs 255 + // 3) custom encoding index reaches 255 // 4) run out of uniqueInfos to encode std::map pageSpecificEncodings; uint32_t space4 = (pageSize - sizeof(unwind_info_compressed_second_level_page_header))/sizeof(uint32_t); - std::vector encodingIndexes; int index = endIndex-1; int entryCount = 0; uint64_t lastEntryAddress = uniqueInfos[index].funcTentAddress; @@ -646,6 +645,7 @@ unsigned int UnwindInfoAtom::makeCompressedSecondLevelPage(const std::vector< std::map::const_iterator pos = commonEncodings.find(info.encoding); if ( pos != commonEncodings.end() ) { encodingIndex = pos->second; + if (_s_log) fprintf(stderr, "makeCompressedSecondLevelPage(): funcIndex=%d, re-use commonEncodings[%d]=0x%08X\n", index, encodingIndex, info.encoding); } else { // no commmon entry, so add one on this page @@ -657,12 +657,13 @@ unsigned int UnwindInfoAtom::makeCompressedSecondLevelPage(const std::vector< std::map::iterator ppos = pageSpecificEncodings.find(encoding); if ( ppos != pageSpecificEncodings.end() ) { encodingIndex = pos->second; + if (_s_log) fprintf(stderr, "makeCompressedSecondLevelPage(): funcIndex=%d, re-use pageSpecificEncodings[%d]=0x%08X\n", index, encodingIndex, encoding); } else { encodingIndex = commonEncodings.size() + pageSpecificEncodings.size(); if ( encodingIndex <= 255 ) { pageSpecificEncodings[encoding] = encodingIndex; - if (_s_log) fprintf(stderr, "makeCompressedSecondLevelPage(): pageSpecificEncodings[%d]=0x%08X\n", encodingIndex, encoding); + if (_s_log) fprintf(stderr, "makeCompressedSecondLevelPage(): funcIndex=%d, pageSpecificEncodings[%d]=0x%08X\n", index, encodingIndex, encoding); } else { canDo = false; // case 3) @@ -671,8 +672,6 @@ unsigned int UnwindInfoAtom::makeCompressedSecondLevelPage(const std::vector< } } } - if ( canDo ) - encodingIndexes.push_back(encodingIndex); // compute function offset uint32_t funcOffsetWithInPage = lastEntryAddress - info.funcTentAddress; if ( funcOffsetWithInPage > 0x00FFFF00 ) { @@ -680,16 +679,16 @@ unsigned int UnwindInfoAtom::makeCompressedSecondLevelPage(const std::vector< canDo = false; // case 2) if (_s_log) fprintf(stderr, "can't use compressed page with %u entries because function offset too big\n", entryCount); } - else { - ++entryCount; - } // check room for entry - if ( (pageSpecificEncodings.size()+entryCount) >= space4 ) { + if ( (pageSpecificEncodings.size()+entryCount) > space4 ) { canDo = false; // case 1) --entryCount; if (_s_log) fprintf(stderr, "end of compressed page with %u entries because full\n", entryCount); } //if (_s_log) fprintf(stderr, "space4=%d, pageSpecificEncodings.size()=%ld, entryCount=%d\n", space4, pageSpecificEncodings.size(), entryCount); + if ( canDo ) { + ++entryCount; + } } // check for cases where it would be better to use a regular (non-compressed) page @@ -725,6 +724,7 @@ unsigned int UnwindInfoAtom::makeCompressedSecondLevelPage(const std::vector< uint8_t encodingIndex; if ( encodingMeansUseDwarf(info.encoding) ) { // dwarf entries are always in page specific encodings + assert(pageSpecificEncodings.find(info.encoding+i) != pageSpecificEncodings.end()); encodingIndex = pageSpecificEncodings[info.encoding+i]; } else { @@ -760,16 +760,22 @@ unsigned int UnwindInfoAtom::makeCompressedSecondLevelPage(const std::vector< - - -static uint64_t calculateEHFrameSize(const ld::Internal& state) +static uint64_t calculateEHFrameSize(ld::Internal& state) { + bool allCIEs = true; uint64_t size = 0; - for (std::vector::const_iterator sit=state.sections.begin(); sit != state.sections.end(); ++sit) { - ld::Internal::FinalSection* sect = *sit; + for (ld::Internal::FinalSection* sect : state.sections) { if ( sect->type() == ld::Section::typeCFI ) { - for (std::vector::iterator ait=sect->atoms.begin(); ait != sect->atoms.end(); ++ait) { - size += (*ait)->size(); + for (const ld::Atom* atom : sect->atoms) { + size += atom->size(); + if ( strcmp(atom->name(), "CIE") != 0 ) + allCIEs = false; + } + if ( allCIEs ) { + // Linker generates eh_frame data even when there's only an unused CIEs in it + sect->atoms.clear(); + state.sections.erase(std::remove(state.sections.begin(), state.sections.end(), sect), state.sections.end()); + return 0; } } } diff --git a/src/ld/passes/objc.cpp b/src/ld/passes/objc.cpp index ad4673c..c65ca21 100644 --- a/src/ld/passes/objc.cpp +++ b/src/ld/passes/objc.cpp @@ -755,6 +755,21 @@ bool OptimizeCategories::hasProperties(ld::Internal& state, const std::vector } +static const ld::Atom* fixClassAliases(const ld::Atom* classAtom) +{ + if ( (classAtom->size() != 0) || (classAtom->definition() == ld::Atom::definitionProxy) ) + return classAtom; + + for (ld::Fixup::iterator fit=classAtom->fixupsBegin(); fit != classAtom->fixupsEnd(); ++fit) { + if ( fit->kind == ld::Fixup::kindNoneFollowOn ) { + assert(fit->offsetInAtom == 0); + assert(fit->binding == ld::Fixup::bindingDirectlyBound); + return fit->u.target; + } + } + + return classAtom; +} // // Helper for std::remove_if @@ -841,7 +856,7 @@ void OptimizeCategories::doit(const Options& opts, ld::Internal& state) // ignore categories also in __objc_nlcatlist if ( nlcatListAtoms.count(categoryAtom) != 0 ) continue; - const ld::Atom* categoryOnClassAtom = Category::getClass(state, categoryAtom, hasAddend); + const ld::Atom* categoryOnClassAtom = fixClassAliases(Category::getClass(state, categoryAtom, hasAddend)); assert(categoryOnClassAtom != NULL); // only look at classes defined in this image if ( categoryOnClassAtom->definition() != ld::Atom::definitionProxy ) { diff --git a/src/ld/passes/stubs/stubs.cpp b/src/ld/passes/stubs/stubs.cpp index bee5f2f..abed7e3 100644 --- a/src/ld/passes/stubs/stubs.cpp +++ b/src/ld/passes/stubs/stubs.cpp @@ -135,8 +135,12 @@ const ld::Atom* Pass::stubableFixup(const ld::Fixup* fixup, ld::Internal& state) if ( (target->definition() == ld::Atom::definitionRegular) && (target->combine() == ld::Atom::combineByName) && ((target->symbolTableInclusion() == ld::Atom::symbolTableIn) - || (target->symbolTableInclusion() == ld::Atom::symbolTableInAndNeverStrip)) ) + || (target->symbolTableInclusion() == ld::Atom::symbolTableInAndNeverStrip)) ) { + // don't make stubs for auto-hide symbols + if ( target->autoHide() && (!_options.hasExportMaskList() || !_options.shouldExport(target->name())) ) + return NULL; return target; + } // create stub if target is interposable if ( _options.interposable(target->name()) ) return target; @@ -174,8 +178,10 @@ ld::Atom* Pass::makeStub(const ld::Atom& target, bool weakImport) if ( (dylib != NULL) && dylib->willBeLazyLoadedDylib() ) forLazyDylib = true; bool stubToResolver = (target.contentType() == ld::Atom::typeResolver); +#if SUPPORT_ARCH_arm_any || SUPPORT_ARCH_arm64 bool usingDataConst = _options.useDataConstSegment(); - +#endif + if ( usingCompressedLINKEDIT() && !forLazyDylib ) { if ( _internal->compressedFastBinderProxy == NULL ) throwf("symbol dyld_stub_binder not found (normally in libSystem.dylib). Needed to perform lazy binding to function %s", target.name()); diff --git a/src/other/machochecker.cpp b/src/other/machochecker.cpp index aec6ebe..0465cd5 100644 --- a/src/other/machochecker.cpp +++ b/src/other/machochecker.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -100,8 +101,8 @@ class MachOChecker { public: static bool validFile(const uint8_t* fileContent); - static MachOChecker* make(const uint8_t* fileContent, uint32_t fileLength, const char* path) - { return new MachOChecker(fileContent, fileLength, path); } + static MachOChecker* make(const uint8_t* fileContent, uint32_t fileLength, const char* path, const char* verifierDstRoot) + { return new MachOChecker(fileContent, fileLength, path, verifierDstRoot); } virtual ~MachOChecker() {} @@ -126,7 +127,7 @@ private: typedef std::unordered_set StringSet; - MachOChecker(const uint8_t* fileContent, uint32_t fileLength, const char* path); + MachOChecker(const uint8_t* fileContent, uint32_t fileLength, const char* path, const char* verifierDstRoot); void checkMachHeader(); void checkLoadCommands(); void checkSection(const macho_segment_command

* segCmd, const macho_section

* sect); @@ -137,6 +138,11 @@ private: void checkRelocations(); void checkExternalReloation(const macho_relocation_info

* reloc); void checkLocalReloation(const macho_relocation_info

* reloc); + void verify(); + void verifyInstallName(); + void verifyNoRpaths(); + void verifyNoFlatLookups(); + pint_t relocBase(); bool addressInWritableSegment(pint_t address); bool hasTextRelocInRange(pint_t start, pint_t end); @@ -145,12 +151,14 @@ private: bool addressIsBindingSite(pint_t addr); pint_t getInitialStackPointer(const macho_thread_command

*); pint_t getEntryPoint(const macho_thread_command

*); - + const char* archName(); const char* fPath; + const char* fDstRoot; const macho_header

* fHeader; uint32_t fLength; + const char* fInstallName; const char* fStrings; const char* fStringsEnd; const macho_nlist

* fSymbols; @@ -164,6 +172,7 @@ private: uint32_t fExternalRelocationsCount; bool fWriteableSegmentWithAddrOver4G; bool fSlidableImage; + bool fHasLC_RPATH; const macho_segment_command

* fFirstSegment; const macho_segment_command

* fFirstWritableSegment; const macho_segment_command

* fTEXTSegment; @@ -173,43 +182,6 @@ private: }; - -template <> -bool MachOChecker::validFile(const uint8_t* fileContent) -{ - const macho_header

* header = (const macho_header

*)fileContent; - if ( header->magic() != MH_MAGIC ) - return false; - if ( header->cputype() != CPU_TYPE_POWERPC ) - return false; - switch (header->filetype()) { - case MH_EXECUTE: - case MH_DYLIB: - case MH_BUNDLE: - case MH_DYLINKER: - return true; - } - return false; -} - -template <> -bool MachOChecker::validFile(const uint8_t* fileContent) -{ - const macho_header

* header = (const macho_header

*)fileContent; - if ( header->magic() != MH_MAGIC_64 ) - return false; - if ( header->cputype() != CPU_TYPE_POWERPC64 ) - return false; - switch (header->filetype()) { - case MH_EXECUTE: - case MH_DYLIB: - case MH_BUNDLE: - case MH_DYLINKER: - return true; - } - return false; -} - template <> bool MachOChecker::validFile(const uint8_t* fileContent) { @@ -246,6 +218,7 @@ bool MachOChecker::validFile(const uint8_t* fileContent) return false; } +#if SUPPORT_ARCH_arm_any template <> bool MachOChecker::validFile(const uint8_t* fileContent) { @@ -263,6 +236,7 @@ bool MachOChecker::validFile(const uint8_t* fileContent) } return false; } +#endif #if SUPPORT_ARCH_arm64 template <> @@ -284,27 +258,12 @@ bool MachOChecker::validFile(const uint8_t* fileContent) } #endif -template <> uint8_t MachOChecker::loadCommandSizeMask() { return 0x03; } -template <> uint8_t MachOChecker::loadCommandSizeMask() { return 0x07; } template <> uint8_t MachOChecker::loadCommandSizeMask() { return 0x03; } template <> uint8_t MachOChecker::loadCommandSizeMask() { return 0x07; } template <> uint8_t MachOChecker::loadCommandSizeMask() { return 0x03; } -#if SUPPORT_ARCH_arm64 template <> uint8_t MachOChecker::loadCommandSizeMask() { return 0x07; } -#endif -template <> -ppc::P::uint_t MachOChecker::getInitialStackPointer(const macho_thread_command* threadInfo) -{ - return threadInfo->thread_register(3); -} - -template <> -ppc64::P::uint_t MachOChecker::getInitialStackPointer(const macho_thread_command* threadInfo) -{ - return threadInfo->thread_register(3); -} template <> x86::P::uint_t MachOChecker::getInitialStackPointer(const macho_thread_command* threadInfo) @@ -324,25 +283,12 @@ arm::P::uint_t MachOChecker::getInitialStackPointer(const macho_thread_comm return threadInfo->thread_register(13); } -#if SUPPORT_ARCH_arm64 template <> arm64::P::uint_t MachOChecker::getInitialStackPointer(const macho_thread_command* threadInfo) { throw "LC_UNIXTHREAD not supported for arm64"; } -#endif -template <> -ppc::P::uint_t MachOChecker::getEntryPoint(const macho_thread_command* threadInfo) -{ - return threadInfo->thread_register(0); -} - -template <> -ppc64::P::uint_t MachOChecker::getEntryPoint(const macho_thread_command* threadInfo) -{ - return threadInfo->thread_register(0); -} template <> x86::P::uint_t MachOChecker::getEntryPoint(const macho_thread_command* threadInfo) @@ -362,19 +308,47 @@ arm::P::uint_t MachOChecker::getEntryPoint(const macho_thread_commandthread_register(15); } -#if SUPPORT_ARCH_arm64 template <> arm64::P::uint_t MachOChecker::getEntryPoint(const macho_thread_command* threadInfo) { throw "LC_UNIXTHREAD not supported for arm64"; } -#endif + + +template +const char* MachOChecker::archName() +{ + switch ( fHeader->cputype() ) { + case CPU_TYPE_I386: + return "i386"; + case CPU_TYPE_X86_64: + if ( fHeader->cpusubtype() == CPU_SUBTYPE_X86_64_H ) + return "x86_64h"; + else + return "x86_64"; + case CPU_TYPE_ARM: + switch ( fHeader->cpusubtype() ) { + case CPU_SUBTYPE_ARM_V7: + return "armv7"; + case CPU_SUBTYPE_ARM_V7S: + return "armv7s"; + case CPU_SUBTYPE_ARM_V7K: + return "armv7k"; + } + return "arm"; + case CPU_TYPE_ARM64: + return "arm64"; + } + return "unknown"; +} + + template -MachOChecker::MachOChecker(const uint8_t* fileContent, uint32_t fileLength, const char* path) - : fHeader(NULL), fLength(fileLength), fStrings(NULL), fSymbols(NULL), fSymbolCount(0), fDynamicSymbolTable(NULL), fIndirectTableCount(0), +MachOChecker::MachOChecker(const uint8_t* fileContent, uint32_t fileLength, const char* path, const char* verifierDstRoot) + : fHeader(NULL), fLength(fileLength), fInstallName(NULL), fStrings(NULL), fSymbols(NULL), fSymbolCount(0), fDynamicSymbolTable(NULL), fIndirectTableCount(0), fLocalRelocations(NULL), fLocalRelocationsCount(0), fExternalRelocations(NULL), fExternalRelocationsCount(0), - fWriteableSegmentWithAddrOver4G(false), fSlidableImage(false), fFirstSegment(NULL), fFirstWritableSegment(NULL), + fWriteableSegmentWithAddrOver4G(false), fSlidableImage(false), fHasLC_RPATH(false), fFirstSegment(NULL), fFirstWritableSegment(NULL), fTEXTSegment(NULL), fDyldInfo(NULL), fSectionCount(0) { // sanity check @@ -382,6 +356,7 @@ MachOChecker::MachOChecker(const uint8_t* fileContent, uint32_t fileLength, c throw "not a mach-o file that can be checked"; fPath = strdup(path); + fDstRoot = verifierDstRoot ? strdup(verifierDstRoot) : NULL; fHeader = (const macho_header

*)fileContent; // sanity check header @@ -397,6 +372,9 @@ MachOChecker::MachOChecker(const uint8_t* fileContent, uint32_t fileLength, c checkSymbolTable(); checkInitTerms(); + + if ( verifierDstRoot != NULL ) + verify(); } @@ -407,7 +385,7 @@ void MachOChecker::checkMachHeader() throw "sizeofcmds in mach_header is larger than file"; uint32_t flags = fHeader->flags(); - const uint32_t invalidBits = MH_INCRLINK | MH_LAZY_INIT | 0xFE000000; + const uint32_t invalidBits = MH_INCRLINK | MH_LAZY_INIT | 0xFC000000; if ( flags & invalidBits ) throw "invalid bits in mach_header flags"; if ( (flags & MH_NO_REEXPORTED_DYLIBS) && (fHeader->filetype() != MH_DYLIB) ) @@ -438,6 +416,7 @@ void MachOChecker::checkLoadCommands() const uint32_t cmd_count = fHeader->ncmds(); const macho_load_command

* const cmds = (macho_load_command

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

)); const macho_load_command

* cmd = cmds; + const macho_dylib_command

* dylibID; for (uint32_t i = 0; i < cmd_count; ++i) { uint32_t size = cmd->cmdsize(); if ( (size & this->loadCommandSizeMask()) != 0 ) @@ -452,9 +431,8 @@ void MachOChecker::checkLoadCommands() case LC_SYMTAB: case LC_DYSYMTAB: case LC_LOAD_DYLIB: - case LC_ID_DYLIB: - case LC_LOAD_DYLINKER: case LC_ID_DYLINKER: + case LC_LOAD_DYLINKER: case macho_routines_command

::CMD: case LC_SUB_FRAMEWORK: case LC_SUB_CLIENT: @@ -469,13 +447,23 @@ void MachOChecker::checkLoadCommands() case LC_LOAD_UPWARD_DYLIB: case LC_VERSION_MIN_MACOSX: case LC_VERSION_MIN_IPHONEOS: - case LC_RPATH: case LC_FUNCTION_STARTS: case LC_DYLD_ENVIRONMENT: case LC_DATA_IN_CODE: case LC_DYLIB_CODE_SIGN_DRS: case LC_SOURCE_VERSION: break; + case LC_RPATH: + fHasLC_RPATH = true; + break; + case LC_ID_DYLIB: + dylibID = (macho_dylib_command

*)cmd; + if ( dylibID->name_offset() > size ) + throwf("malformed mach-o: LC_ID_DYLIB load command has offset (%u) outside its size (%u)", dylibID->name_offset(), size); + if ( (dylibID->name_offset() + strlen(dylibID->name()) + 1) > size ) + throwf("malformed mach-o: LC_ID_DYLIB load command string extends beyond end of load command"); + fInstallName = dylibID->name(); + break; case LC_DYLD_INFO: case LC_DYLD_INFO_ONLY: fDyldInfo = (macho_dyld_info_command

*)cmd; @@ -533,7 +521,7 @@ void MachOChecker::checkLoadCommands() else { throw "overlapping segment vm addresses"; } - segmentAddressRanges.push_back(std::make_pair(startAddr, endAddr)); + segmentAddressRanges.push_back(std::make_pair(startAddr, endAddr)); } // see if this overlaps another segment file offset range uint64_t startOffset = segCmd->fileoff(); @@ -550,7 +538,7 @@ void MachOChecker::checkLoadCommands() else { throw "overlapping segment file data"; } - segmentFileOffsetRanges.push_back(std::make_pair(startOffset, endOffset)); + segmentFileOffsetRanges.push_back(std::make_pair(startOffset, endOffset)); // check is within file bounds if ( (startOffset > fLength) || (endOffset > fLength) ) throw "segment file data is past end of file"; @@ -690,6 +678,16 @@ void MachOChecker::checkLoadCommands() } } + // verify dylib has LC_ID_DYLIB + if ( fHeader->filetype() == MH_DYLIB ) { + if ( fInstallName == NULL ) + throw "MH_DYLIB missing LC_ID_DYLIB"; + } + else { + if ( fInstallName != NULL ) + throw "LC_ID_DYLIB found but file type is not MH_DYLIB"; + } + // check LC_SYMTAB, LC_DYSYMTAB, and LC_SEGMENT_SPLIT_INFO cmd = cmds; bool foundDynamicSymTab = false; @@ -702,8 +700,8 @@ void MachOChecker::checkLoadCommands() fSymbols = (const macho_nlist

*)((char*)fHeader + symtab->symoff()); if ( symtab->symoff() < linkEditSegment->fileoff() ) throw "symbol table not in __LINKEDIT"; - if ( (symtab->symoff() + fSymbolCount*sizeof(macho_nlist

*)) > (linkEditSegment->fileoff()+linkEditSegment->filesize()) ) - throw "symbol table end not in __LINKEDIT"; + if ( (symtab->symoff() + fSymbolCount*sizeof(macho_nlist

*)) > symtab->stroff() ) + throw "symbol table overlaps string pool"; if ( (symtab->symoff() % sizeof(pint_t)) != 0 ) throw "symbol table start not pointer aligned"; fStrings = (char*)fHeader + symtab->stroff(); @@ -837,7 +835,85 @@ void MachOChecker::checkSection(const macho_segment_command

* segCmd, const } +template +void MachOChecker::verify() +{ + bool sharedCacheCandidate = false; + if ( fInstallName != NULL ) { + if ( (strncmp(fInstallName, "/usr/lib/", 9) == 0) || (strncmp(fInstallName, "/System/Library/", 16) == 0) ) { + sharedCacheCandidate = true; + verifyInstallName(); + verifyNoRpaths(); + } + } + verifyNoFlatLookups(); +} + + +template +void MachOChecker::verifyInstallName() +{ + // Don't allow @rpath to be used as -install_name for OS dylibs + if ( strncmp(fInstallName, "@rpath/", 7) == 0 ) { + printf("os_dylib_rpath_install_name\tfatal\t-install_name uses @rpath in arch %s\n", archName()); + } + else { + // Verify -install_name match actual path of dylib + const char* installPathWithinDstRoot = &fPath[strlen(fDstRoot)]; + if ( strcmp(installPathWithinDstRoot, fInstallName) != 0 ) { + // see if install name is a symlink to actual file + bool symlinkToDylib = false; + char absDstPath[PATH_MAX]; + if ( realpath(fDstRoot, absDstPath) != NULL ) { + char fullInstallNamePath[PATH_MAX]; + strlcpy(fullInstallNamePath, absDstPath, PATH_MAX); + strlcat(fullInstallNamePath, fInstallName, PATH_MAX); + char absInstallNamePath[PATH_MAX]; + if ( realpath(fullInstallNamePath, absInstallNamePath) != NULL ) { + char absFPath[PATH_MAX]; + if ( realpath(fPath, absFPath) != NULL ) { + if ( strcmp(absInstallNamePath, absFPath) == 0 ) + symlinkToDylib = true; + } + } + } + if ( !symlinkToDylib ) + printf("os_dylib_bad_install_name\twarn\t-install_name does not match install location in arch %s\n", archName()); + } + } +} + +template +void MachOChecker::verifyNoRpaths() +{ + // Don't allow OS dylibs to add rpaths + if ( fHasLC_RPATH ) { + printf("os_dylib_rpath\twarn\tcontains LC_RPATH load command in arch %s\n", archName()); + } +} + + +template +void MachOChecker::verifyNoFlatLookups() +{ + if ( (fHeader->flags() & MH_TWOLEVEL) == 0 ) { + printf("os_dylib_flat_namespace\twarn\tbuilt with -flat_namespace in arch %s\n", archName()); + return; + } + + if ( fDynamicSymbolTable != NULL ) { + const macho_nlist

* const undefinesStart = &fSymbols[fDynamicSymbolTable->iundefsym()]; + const macho_nlist

* const undefinesEnd = &undefinesStart[fDynamicSymbolTable->nundefsym()]; + for(const macho_nlist

* sym = undefinesStart; sym < undefinesEnd; ++sym) { + //printf("0x%04X %s\n", sym->n_desc(), &fStrings[sym->n_strx()]); + if ( GET_LIBRARY_ORDINAL(sym->n_desc()) == DYNAMIC_LOOKUP_ORDINAL ) { + const char* symName = &fStrings[sym->n_strx()]; + printf("os_dylib_undefined_dynamic_lookup\twarn\tbuilt with -undefined dynamic_lookup for symbol %s in arch %s\n", symName, archName()); + } + } + } +} template void MachOChecker::checkIndirectSymbolTable() @@ -879,6 +955,44 @@ void MachOChecker::checkIndirectSymbolTable() } cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); } + + + if ( fDynamicSymbolTable->ilocalsym() != 0 ) + throwf("start of local symbols (%d) not at start of symbol table", fDynamicSymbolTable->ilocalsym()); + + if ( fDynamicSymbolTable->ilocalsym() > fSymbolCount ) + throwf("start of local symbols out of range (%d > %d) in indirect symbol table", fDynamicSymbolTable->ilocalsym(), fSymbolCount); + if ( fDynamicSymbolTable->ilocalsym() + fDynamicSymbolTable->nlocalsym() > fSymbolCount ) { + throwf("local symbols out of range (%d+%d > %d) in indirect symbol table", + fDynamicSymbolTable->ilocalsym(), fDynamicSymbolTable->nlocalsym(), fSymbolCount); + } + + if ( fDynamicSymbolTable->iextdefsym() > fSymbolCount ) + throwf("start of extern symbols out of range (%d > %d) in indirect symbol table", fDynamicSymbolTable->iextdefsym(), fSymbolCount); + if ( fDynamicSymbolTable->iextdefsym() != fDynamicSymbolTable->ilocalsym() + fDynamicSymbolTable->nlocalsym() ) { + throwf("start of extern symbols (%d) not contiguous to local symbols (%d+%d) in indirect symbol table", + fDynamicSymbolTable->iextdefsym(), fDynamicSymbolTable->ilocalsym(), fDynamicSymbolTable->nlocalsym() ); + } + if ( fDynamicSymbolTable->iextdefsym() + fDynamicSymbolTable->nextdefsym() > fSymbolCount ) { + throwf("extern symbols out of range (%d+%d > %d) in indirect symbol table", + fDynamicSymbolTable->iextdefsym(), fDynamicSymbolTable->nextdefsym(), fSymbolCount); + } + + if ( fDynamicSymbolTable->iundefsym() > fSymbolCount ) + throwf("start of undefined symbols out of range (%d > %d) in indirect symbol table", fDynamicSymbolTable->iundefsym(), fSymbolCount); + if ( fDynamicSymbolTable->iundefsym() != fDynamicSymbolTable->iextdefsym() + fDynamicSymbolTable->nextdefsym() ) { + throwf("start of undefined symbols (%d) not contiguous to extern symbols (%d+%d) in indirect symbol table", + fDynamicSymbolTable->iundefsym(), fDynamicSymbolTable->iextdefsym(), fDynamicSymbolTable->nextdefsym()); + } + if ( fDynamicSymbolTable->iundefsym() + fDynamicSymbolTable->nundefsym() > fSymbolCount ) { + throwf("undefined symbols out of range (%d+%d > %d) in indirect symbol table", + fDynamicSymbolTable->iundefsym(), fDynamicSymbolTable->nundefsym(), fSymbolCount); + } + + if ( fDynamicSymbolTable->iundefsym() + fDynamicSymbolTable->nundefsym() != fSymbolCount ) { + throwf("end undefined symbols (%d+%d) not at end of all symbols (%d) in indirect symbol table", + fDynamicSymbolTable->iundefsym(), fDynamicSymbolTable->nundefsym(), fSymbolCount ); + } } @@ -985,23 +1099,6 @@ void MachOChecker::checkInitTerms() } -template <> -ppc::P::uint_t MachOChecker::relocBase() -{ - if ( fHeader->flags() & MH_SPLIT_SEGS ) - return fFirstWritableSegment->vmaddr(); - else - return fFirstSegment->vmaddr(); -} - -template <> -ppc64::P::uint_t MachOChecker::relocBase() -{ - if ( fWriteableSegmentWithAddrOver4G ) - return fFirstWritableSegment->vmaddr(); - else - return fFirstSegment->vmaddr(); -} template <> x86::P::uint_t MachOChecker::relocBase() @@ -1028,15 +1125,11 @@ arm::P::uint_t MachOChecker::relocBase() return fFirstSegment->vmaddr(); } -#if SUPPORT_ARCH_arm64 template <> arm64::P::uint_t MachOChecker::relocBase() { return fFirstWritableSegment->vmaddr(); } -#endif - - template bool MachOChecker::addressInWritableSegment(pint_t address) @@ -1068,37 +1161,6 @@ bool MachOChecker::addressInWritableSegment(pint_t address) } -template <> -void MachOChecker::checkExternalReloation(const macho_relocation_info

* reloc) -{ - if ( reloc->r_length() != 2 ) - throw "bad external relocation length"; - if ( reloc->r_type() != GENERIC_RELOC_VANILLA ) - throw "unknown external relocation type"; - if ( reloc->r_pcrel() != 0 ) - throw "bad external relocation pc_rel"; - if ( reloc->r_extern() == 0 ) - throw "local relocation found with external relocations"; - if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) ) - throw "external relocation address not in writable segment"; - // FIX: check r_symbol -} - -template <> -void MachOChecker::checkExternalReloation(const macho_relocation_info

* reloc) -{ - if ( reloc->r_length() != 3 ) - throw "bad external relocation length"; - if ( reloc->r_type() != GENERIC_RELOC_VANILLA ) - throw "unknown external relocation type"; - if ( reloc->r_pcrel() != 0 ) - throw "bad external relocation pc_rel"; - if ( reloc->r_extern() == 0 ) - throw "local relocation found with external relocations"; - if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) ) - throw "external relocation address not in writable segment"; - // FIX: check r_symbol -} template <> void MachOChecker::checkExternalReloation(const macho_relocation_info

* reloc) @@ -1139,7 +1201,7 @@ void MachOChecker::checkExternalReloation(const macho_relocation_info

* r { if ( reloc->r_length() != 2 ) throw "bad external relocation length"; - if ( reloc->r_type() != ARM_RELOC_VANILLA ) + if ( reloc->r_type() != ARM_RELOC_VANILLA ) throw "unknown external relocation type"; if ( reloc->r_pcrel() != 0 ) throw "bad external relocation pc_rel"; @@ -1160,38 +1222,6 @@ void MachOChecker::checkExternalReloation(const macho_relocation_info

* #endif -template <> -void MachOChecker::checkLocalReloation(const macho_relocation_info

* reloc) -{ - if ( reloc->r_address() & R_SCATTERED ) { - // scattered - const macho_scattered_relocation_info

* sreloc = (const macho_scattered_relocation_info

*)reloc; - // FIX - - } - else { - // FIX - if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) ) - throwf("local relocation address 0x%08X not in writable segment", reloc->r_address()); - } -} - - -template <> -void MachOChecker::checkLocalReloation(const macho_relocation_info

* reloc) -{ - if ( reloc->r_length() != 3 ) - throw "bad local relocation length"; - if ( reloc->r_type() != GENERIC_RELOC_VANILLA ) - throw "unknown local relocation type"; - if ( reloc->r_pcrel() != 0 ) - throw "bad local relocation pc_rel"; - if ( reloc->r_extern() != 0 ) - throw "external relocation found with local relocations"; - if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) ) - throw "local relocation address not in writable segment"; -} - template <> void MachOChecker::checkLocalReloation(const macho_relocation_info

* reloc) { @@ -1244,7 +1274,6 @@ void MachOChecker::checkLocalReloation(const macho_relocation_info

* re } #endif - template void MachOChecker::checkRelocations() { @@ -1585,7 +1614,7 @@ bool MachOChecker::addressIsBindingSite(pint_t targetAddr) } -static void check(const char* path) +static void check(const char* path, const char* verifierDstRoot) { struct stat stat_buf; @@ -1610,37 +1639,33 @@ static void check(const char* path) unsigned int cputype = OSSwapBigToHostInt32(archs[i].cputype); switch(cputype) { - case CPU_TYPE_POWERPC: - if ( MachOChecker::validFile(p + offset) ) - MachOChecker::make(p + offset, size, path); - else - throw "in universal file, ppc slice does not contain ppc mach-o"; - break; case CPU_TYPE_I386: if ( MachOChecker::validFile(p + offset) ) - MachOChecker::make(p + offset, size, path); + MachOChecker::make(p + offset, size, path, verifierDstRoot); else throw "in universal file, i386 slice does not contain i386 mach-o"; break; - case CPU_TYPE_POWERPC64: - if ( MachOChecker::validFile(p + offset) ) - MachOChecker::make(p + offset, size, path); - else - throw "in universal file, ppc64 slice does not contain ppc64 mach-o"; - break; case CPU_TYPE_X86_64: if ( MachOChecker::validFile(p + offset) ) - MachOChecker::make(p + offset, size, path); + MachOChecker::make(p + offset, size, path, verifierDstRoot); else throw "in universal file, x86_64 slice does not contain x86_64 mach-o"; break; #if SUPPORT_ARCH_arm_any case CPU_TYPE_ARM: if ( MachOChecker::validFile(p + offset) ) - MachOChecker::make(p + offset, size, path); + MachOChecker::make(p + offset, size, path, verifierDstRoot); else throw "in universal file, arm slice does not contain arm mach-o"; break; +#endif +#if SUPPORT_ARCH_arm64 + case CPU_TYPE_ARM64: + if ( MachOChecker::validFile(p + offset) ) + MachOChecker::make(p + offset, size, path, verifierDstRoot); + else + throw "in universal file, arm64 slice does not contain arm mach-o"; + break; #endif default: throwf("in universal file, unknown architecture slice 0x%x\n", cputype); @@ -1648,25 +1673,19 @@ static void check(const char* path) } } else if ( MachOChecker::validFile(p) ) { - MachOChecker::make(p, length, path); - } - else if ( MachOChecker::validFile(p) ) { - MachOChecker::make(p, length, path); - } - else if ( MachOChecker::validFile(p) ) { - MachOChecker::make(p, length, path); + MachOChecker::make(p, length, path, verifierDstRoot); } else if ( MachOChecker::validFile(p) ) { - MachOChecker::make(p, length, path); + MachOChecker::make(p, length, path, verifierDstRoot); } #if SUPPORT_ARCH_arm_any else if ( MachOChecker::validFile(p) ) { - MachOChecker::make(p, length, path); + MachOChecker::make(p, length, path, verifierDstRoot); } #endif #if SUPPORT_ARCH_arm64 else if ( MachOChecker::validFile(p) ) { - MachOChecker::make(p, length, path); + MachOChecker::make(p, length, path, verifierDstRoot); } #endif else { @@ -1682,6 +1701,7 @@ static void check(const char* path) int main(int argc, const char* argv[]) { bool progress = false; + const char* verifierDstRoot = NULL; int result = 0; for(int i=1; i < argc; ++i) { const char* arg = argv[i]; @@ -1689,6 +1709,18 @@ int main(int argc, const char* argv[]) if ( strcmp(arg, "-progress") == 0 ) { progress = true; } + else if ( strcmp(arg, "-verifier_dstroot") == 0 ) { + verifierDstRoot = argv[++i]; + } + else if ( strcmp(arg, "-verifier_error_list") == 0 ) { + printf("os_dylib_rpath_install_name\tOS dylibs (those in /usr/lib/ or /System/Library/) must be built with -install_name that is an absolute path - not an @rpath\n"); + printf("os_dylib_bad_install_name\tOS dylibs (those in /usr/lib/ or /System/Library/) must be built with -install_name matching their file system location\n"); + printf("os_dylib_rpath\tOS dylibs should not contain LC_RPATH load commands (from -rpath linker option)\n"); + printf("os_dylib_flat_namespace\tOS dylibs should not be built with -flat_namespace\n"); + printf("os_dylib_undefined_dynamic_lookup\tOS dylibs should not be built with -undefined dynamic_lookup\n"); + printf("os_dylib_malformed\the mach-o is malformed\n"); + return 0; + } else { throwf("unknown option: %s\n", arg); } @@ -1696,12 +1728,17 @@ int main(int argc, const char* argv[]) else { bool success = true; try { - check(arg); + check(arg, verifierDstRoot); } catch (const char* msg) { - fprintf(stderr, "machocheck failed: %s %s\n", arg, msg); - result = 1; - success = false; + if ( verifierDstRoot ) { + printf("os_dylib_malformed\twarn\t%s\n", msg); + } + else { + fprintf(stderr, "machocheck failed: %s\n", msg); + result = 1; + success = false; + } } if ( success && progress ) printf("ok: %s\n", arg); @@ -1711,5 +1748,3 @@ int main(int argc, const char* argv[]) return result; } - - diff --git a/src/other/unwinddump.cpp b/src/other/unwinddump.cpp index f91ece3..bc925fb 100644 --- a/src/other/unwinddump.cpp +++ b/src/other/unwinddump.cpp @@ -240,7 +240,7 @@ const char* UnwindPrinter::functionName(pint_t addr, uint32_t* offset) for (uint32_t i=0; i < fSymbolCount; ++i) { uint8_t type = fSymbols[i].n_type(); if ( ((type & N_STAB) == 0) && ((type & N_TYPE) == N_SECT) ) { - uint32_t value = fSymbols[i].n_value(); + pint_t value = fSymbols[i].n_value(); if ( value == addr ) { const char* r = &fStrings[fSymbols[i].n_strx()]; return r; diff --git a/unit-tests/run-all-unit-tests b/unit-tests/run-all-unit-tests index 9285128..cdab927 100755 --- a/unit-tests/run-all-unit-tests +++ b/unit-tests/run-all-unit-tests @@ -5,8 +5,6 @@ unset RC_TRACE_ARCHIVES unset LD_TRACE_DYLIBS unset LD_TRACE_ARCHIVES -export DYLD_FALLBACK_LIBRARY_PATH=${DYLD_FALLBACK_LIBRARY_PATH}:/Developer/usr/lib -export MACOSX_DEPLOYMENT_TARGET=10.7 # cd into test-cases directory cd `echo "$0" | sed 's/run-all-unit-tests/test-cases/'` diff --git a/unit-tests/test-cases/linker-optimization-hints/AdrpLdrGotLdrField.s b/unit-tests/test-cases/linker-optimization-hints/AdrpLdrGotLdrField.s new file mode 100644 index 0000000..477cdab --- /dev/null +++ b/unit-tests/test-cases/linker-optimization-hints/AdrpLdrGotLdrField.s @@ -0,0 +1,53 @@ + +#ifndef TARGET + #define TARGET _foo +#endif + + .text + .align 2 +_test: + nop +L1: adrp x0, TARGET@GOTPAGE +L2: ldr x1, [x0, #TARGET@GOTPAGEOFF] +#if LOAD_GPR_8 +L3: ldr b2, [x1, #8] +#elif LOAD_GPR_16 +L3: ldr h2, [x1, #8] +#elif LOAD_GPR_32 +L3: ldr w2, [x1, #8] +#elif LOAD_GPR_64 +L3: ldr x2, [x1, #8] +#elif LOAD_FPR_32 +L3: ldr s2, [x1, #8] +#elif LOAD_FPR_64 +L3: ldr d2, [x1, #8] +#elif LOAD_VEC_128 +L3: ldr q2, [x1, #16] +#endif + nop + + .loh AdrpLdrGotLdr L1, L2, L3 + +#if PADDING +_pad: + .space 1100000 +#endif + +#if FOO_AS_CONST + .const + .align 4 +#endif + +#if FOO_AS_DATA + .data +_makePageOffsetNonZero: .long 0,0,0,0 +#endif + +#if MISALIGN_DATA +_junk: .byte 0 +#endif + +_foo: .long 0,0,0,0 + + + diff --git a/unit-tests/test-cases/linker-optimization-hints/Makefile b/unit-tests/test-cases/linker-optimization-hints/Makefile index de5da0d..8662288 100644 --- a/unit-tests/test-cases/linker-optimization-hints/Makefile +++ b/unit-tests/test-cases/linker-optimization-hints/Makefile @@ -34,7 +34,7 @@ all-x86_64: skip all-armv6: skip all-armv7: skip -all-arm64: AdrpAdd AdrpAddLdr AdrpLdr AdrpLdrGotLdr AdrpAddStr AdrpLdrGotStr AdrpLdrGot +all-arm64: AdrpAdd AdrpAddLdr AdrpLdr AdrpLdrGotLdr AdrpLdrGotLdrField AdrpAddStr AdrpLdrGotStr AdrpLdrGot main.o: ${CC} ${CCFLAGS} main.s -c -o main.o @@ -81,14 +81,14 @@ AdrpAddLdr-ldr: main.o ${CC} ${CCFLAGS} AdrpAddLdr.s -c -o AdrpAddLdr-ldr-g8.o -DFOO_AS_CONST -DLOAD_GPR_8 ${CC} ${CCFLAGS} AdrpAddLdr-ldr-g8.o main.o -o AdrpAddLdr-ldr-g8.exe ${OTOOL} -tV AdrpAddLdr-ldr-g8.exe | grep 'adr x0' | ${FAIL_IF_EMPTY} - ${OTOOL} -tV AdrpAddLdr-ldr-g8.exe | grep 'ldr\tb1, \[x0\]' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpAddLdr-ldr-g8.exe | grep 'ldr\s*b1, \[x0\]' | ${FAIL_IF_EMPTY} ${OTOOL} -tV AdrpAddLdr-ldr-g8.exe | grep 'add x0' | ${FAIL_IF_STDIN} # test ADRP/ADD/LD -> ADR/LDR when target is in __TEXT for 16-bit load ${CC} ${CCFLAGS} AdrpAddLdr.s -c -o AdrpAddLdr-ldr-g16.o -DFOO_AS_CONST -DLOAD_GPR_16 ${CC} ${CCFLAGS} AdrpAddLdr-ldr-g16.o main.o -o AdrpAddLdr-ldr-g16.exe ${OTOOL} -tV AdrpAddLdr-ldr-g16.exe | grep 'adr x0' | ${FAIL_IF_EMPTY} - ${OTOOL} -tV AdrpAddLdr-ldr-g16.exe | grep 'ldr\th1, \[x0\]' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpAddLdr-ldr-g16.exe | grep 'ldr\s*h1, \[x0\]' | ${FAIL_IF_EMPTY} ${OTOOL} -tV AdrpAddLdr-ldr-g16.exe | grep 'add x0' | ${FAIL_IF_STDIN} # test ADRP/ADD/LD -> LDR when target is in __TEXT for 32-bit load @@ -101,7 +101,7 @@ AdrpAddLdr-ldr: main.o # test ADRP/ADD/LD -> LDR when target is in __TEXT for 64-bit load ${CC} ${CCFLAGS} AdrpAddLdr.s -c -o AdrpAddLdr-ldr-g64.o -DFOO_AS_CONST -DLOAD_GPR_64 ${CC} ${CCFLAGS} AdrpAddLdr-ldr-g64.o main.o -o AdrpAddLdr-ldr-g64.exe - ${OTOOL} -tV AdrpAddLdr-ldr-g64.exe | grep 'ldr x1, _foo' | ${FAIL_IF_EMPTY} +# ${OTOOL} -tV AdrpAddLdr-ldr-g64.exe | grep 'ldr x1, _foo' | ${FAIL_IF_EMPTY} ${OTOOL} -tV AdrpAddLdr-ldr-g64.exe | grep 'adrp x0' | ${FAIL_IF_STDIN} ${OTOOL} -tV AdrpAddLdr-ldr-g64.exe | grep 'add x0' | ${FAIL_IF_STDIN} @@ -228,51 +228,51 @@ AdrpAddLdr-seg: # test ADRP/ADD/LD -> ADRP/LD when target is in movable segment for 8-bit load ${CC} ${CCFLAGS} AdrpAddLdr.s -c -o AdrpAddLdr-seg-g8.o -DFOO_AS_DATA -DLOAD_GPR_8 ${CC} ${CCFLAGS} AdrpAddLdr-seg-g8.o -dynamiclib -o AdrpAddLdr-seg-g8.dylib -install_name /usr/lib/libjunk.dylib - ${OTOOL} -tV AdrpAddLdr-seg-g8.dylib | grep 'adrp x0' | ${FAIL_IF_EMPTY} - ${OTOOL} -tV AdrpAddLdr-seg-g8.dylib | grep 'add x0' | ${FAIL_IF_STDIN} - ${OTOOL} -tV AdrpAddLdr-seg-g8.dylib | grep 'ldr b1, \[x0,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpAddLdr-seg-g8.dylib | grep 'adrp\s*x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpAddLdr-seg-g8.dylib | grep 'add\s*x0' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpAddLdr-seg-g8.dylib | grep 'ldr\s*b1, \[x0,' | ${FAIL_IF_EMPTY} # test ADRP/ADD/LD -> ADRP/LD when target is in movable segment for 16-bit load ${CC} ${CCFLAGS} AdrpAddLdr.s -c -o AdrpAddLdr-seg-g16.o -DFOO_AS_DATA -DLOAD_GPR_16 ${CC} ${CCFLAGS} AdrpAddLdr-seg-g16.o -dynamiclib -o AdrpAddLdr-seg-g16.dylib -install_name /usr/lib/libjunk.dylib - ${OTOOL} -tV AdrpAddLdr-seg-g16.dylib | grep 'adrp x0' | ${FAIL_IF_EMPTY} - ${OTOOL} -tV AdrpAddLdr-seg-g16.dylib | grep 'add x0' | ${FAIL_IF_STDIN} - ${OTOOL} -tV AdrpAddLdr-seg-g16.dylib | grep 'ldr h1, \[x0,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpAddLdr-seg-g16.dylib | grep 'adrp\s*x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpAddLdr-seg-g16.dylib | grep 'add\s*x0' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpAddLdr-seg-g16.dylib | grep 'ldr\s*h1, \[x0,' | ${FAIL_IF_EMPTY} # test ADRP/ADD/LD -> ADRP/LD when target is in movable segment for 32-bit load ${CC} ${CCFLAGS} AdrpAddLdr.s -c -o AdrpAddLdr-seg-g32.o -DFOO_AS_DATA -DLOAD_GPR_32 ${CC} ${CCFLAGS} AdrpAddLdr-seg-g32.o -dynamiclib -o AdrpAddLdr-seg-g32.dylib -install_name /usr/lib/libjunk.dylib - ${OTOOL} -tV AdrpAddLdr-seg-g32.dylib | grep 'adrp x0' | ${FAIL_IF_EMPTY} - ${OTOOL} -tV AdrpAddLdr-seg-g32.dylib | grep 'add x0' | ${FAIL_IF_STDIN} - ${OTOOL} -tV AdrpAddLdr-seg-g32.dylib | grep 'ldr w1, \[x0,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpAddLdr-seg-g32.dylib | grep 'adrp\s*x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpAddLdr-seg-g32.dylib | grep 'add\s*x0' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpAddLdr-seg-g32.dylib | grep 'ldr\s*w1, \[x0,' | ${FAIL_IF_EMPTY} # test ADRP/ADD/LD -> ADRP/LD when target is in movable segment for 64-bit load ${CC} ${CCFLAGS} AdrpAddLdr.s -c -o AdrpAddLdr-seg-g64.o -DFOO_AS_DATA -DLOAD_GPR_64 ${CC} ${CCFLAGS} AdrpAddLdr-seg-g64.o -dynamiclib -o AdrpAddLdr-seg-g64.dylib -install_name /usr/lib/libjunk.dylib - ${OTOOL} -tV AdrpAddLdr-seg-g64.dylib | grep 'adrp x0' | ${FAIL_IF_EMPTY} - ${OTOOL} -tV AdrpAddLdr-seg-g64.dylib | grep 'add x0' | ${FAIL_IF_STDIN} - ${OTOOL} -tV AdrpAddLdr-seg-g64.dylib | grep 'ldr x1, \[x0,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpAddLdr-seg-g64.dylib | grep 'adrp\s*x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpAddLdr-seg-g64.dylib | grep 'add\s*x0' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpAddLdr-seg-g64.dylib | grep 'ldr\s*x1, \[x0,' | ${FAIL_IF_EMPTY} # test ADRP/ADD/LD -> ADRP/LD when target is in movable segment for 32-bit fp load ${CC} ${CCFLAGS} AdrpAddLdr.s -c -o AdrpAddLdr-seg-f32.o -DFOO_AS_DATA -DLOAD_FPR_32 ${CC} ${CCFLAGS} AdrpAddLdr-seg-f32.o -dynamiclib -o AdrpAddLdr-seg-f32.dylib -install_name /usr/lib/libjunk.dylib - ${OTOOL} -tV AdrpAddLdr-seg-f32.dylib | grep 'adrp x0' | ${FAIL_IF_EMPTY} - ${OTOOL} -tV AdrpAddLdr-seg-f32.dylib | grep 'add x0' | ${FAIL_IF_STDIN} - ${OTOOL} -tV AdrpAddLdr-seg-f32.dylib | grep 'ldr s1, \[x0,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpAddLdr-seg-f32.dylib | grep 'adrp\s*x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpAddLdr-seg-f32.dylib | grep 'add\s*x0' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpAddLdr-seg-f32.dylib | grep 'ldr\s*s1, \[x0,' | ${FAIL_IF_EMPTY} # test ADRP/ADD/LD -> ADRP/LD when target is in movable segment for 64-bit fp load ${CC} ${CCFLAGS} AdrpAddLdr.s -c -o AdrpAddLdr-seg-f64.o -DFOO_AS_DATA -DLOAD_FPR_64 ${CC} ${CCFLAGS} AdrpAddLdr-seg-f64.o -dynamiclib -o AdrpAddLdr-seg-f64.dylib -install_name /usr/lib/libjunk.dylib - ${OTOOL} -tV AdrpAddLdr-seg-f64.dylib | grep 'adrp x0' | ${FAIL_IF_EMPTY} - ${OTOOL} -tV AdrpAddLdr-seg-f64.dylib | grep 'add x0' | ${FAIL_IF_STDIN} - ${OTOOL} -tV AdrpAddLdr-seg-f64.dylib | grep 'ldr d1, \[x0,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpAddLdr-seg-f64.dylib | grep 'adrp\s*x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpAddLdr-seg-f64.dylib | grep 'add\s*x0' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpAddLdr-seg-f64.dylib | grep 'ldr\s*d1, \[x0,' | ${FAIL_IF_EMPTY} # test ADRP/ADD/LD -> ADRP/LD when target is in movable segmentfor 128-bit vec load ${CC} ${CCFLAGS} AdrpAddLdr.s -c -o AdrpAddLdr-seg-v128.o -DFOO_AS_DATA -DLOAD_VEC_128 ${CC} ${CCFLAGS} AdrpAddLdr-seg-v128.o -dynamiclib -o AdrpAddLdr-seg-v128.dylib -install_name /usr/lib/libjunk.dylib - ${OTOOL} -tV AdrpAddLdr-seg-v128.dylib | grep 'adrp x0' | ${FAIL_IF_EMPTY} - ${OTOOL} -tV AdrpAddLdr-seg-v128.dylib | grep 'add x0' | ${FAIL_IF_STDIN} - ${OTOOL} -tV AdrpAddLdr-seg-v128.dylib | grep 'ldr q1, \[x0,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpAddLdr-seg-v128.dylib | grep 'adrp\s*x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpAddLdr-seg-v128.dylib | grep 'add\s*x0' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpAddLdr-seg-v128.dylib | grep 'ldr\s*q1, \[x0,' | ${FAIL_IF_EMPTY} @@ -812,6 +812,322 @@ AdrpLdrGotLdr-farunaligned: main.o ${OTOOL} -tV AdrpLdrGotLdr-farunaligned-v128.exe | grep 'ldr\tq2, \[x1\]' | ${FAIL_IF_EMPTY} +AdrpLdrGotLdrField: AdrpLdrGotLdrField-extern AdrpLdrGotLdrField-externfargot AdrpLdrGotLdrField-near AdrpLdrGotLdrField-far AdrpLdrGotLdrField-nearunaligned AdrpLdrGotLdrField-farunaligned + true + +AdrpLdrGotLdrField-extern: main.o + # test ADRP/LDR/LDR -> LDR/LDR when GOT slot is close for 8-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-extern-g8.o -DFOO_AS_CONST -DLOAD_GPR_8 -DTARGET=_malloc + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-extern-g8.o main.o -o AdrpLdrGotLdrField-extern-g8.exe + ${OTOOL} -tV AdrpLdrGotLdrField-extern-g8.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-extern-g8.exe | grep 'ldr\s*x1,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-extern-g8.exe | grep 'ldr\s*b2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -> LDR/LDR when GOT slot is close for 16-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-extern-g16.o -DFOO_AS_CONST -DLOAD_GPR_16 -DTARGET=_malloc + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-extern-g16.o main.o -o AdrpLdrGotLdrField-extern-g16.exe + ${OTOOL} -tV AdrpLdrGotLdrField-extern-g16.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-extern-g16.exe | grep 'ldr\s*x1,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-extern-g16.exe | grep 'ldr\s*h2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -> LDR/LDR when GOT slot is close for 32-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-extern-g32.o -DFOO_AS_CONST -DLOAD_GPR_32 -DTARGET=_malloc + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-extern-g32.o main.o -o AdrpLdrGotLdrField-extern-g32.exe + ${OTOOL} -tV AdrpLdrGotLdrField-extern-g32.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-extern-g32.exe | grep 'ldr\s*x1,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-extern-g32.exe | grep 'ldr\s*w2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -> LDR/LDR when GOT slot is close for 64-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-extern-g64.o -DFOO_AS_CONST -DLOAD_GPR_64 -DTARGET=_malloc + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-extern-g64.o main.o -o AdrpLdrGotLdrField-extern-g64.exe + ${OTOOL} -tV AdrpLdrGotLdrField-extern-g64.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-extern-g64.exe | grep 'ldr\s*x1,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-extern-g64.exe | grep 'ldr\s*x2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -> LDR/LDR when GOT slot is close for 32-bit fp load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-extern-f32.o -DFOO_AS_CONST -DLOAD_FPR_32 -DTARGET=_malloc + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-extern-f32.o main.o -o AdrpLdrGotLdrField-extern-f32.exe + ${OTOOL} -tV AdrpLdrGotLdrField-extern-f32.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-extern-f32.exe | grep 'ldr\s*x1,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-extern-f32.exe | grep 'ldr\s*s2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -> LDR/LDR when GOT slot is close for 64-bit fp load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-extern-f64.o -DFOO_AS_CONST -DLOAD_FPR_64 -DTARGET=_malloc + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-extern-f64.o main.o -o AdrpLdrGotLdrField-extern-f64.exe + ${OTOOL} -tV AdrpLdrGotLdrField-extern-f64.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-extern-f64.exe | grep 'ldr\s*x1,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-extern-f64.exe | grep 'ldr\s*d2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -> LDR/LDR when GOT slot is close for 128-bit vec load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-extern-v128.o -DFOO_AS_CONST -DLOAD_VEC_128 -DTARGET=_malloc + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-extern-v128.o main.o -o AdrpLdrGotLdrField-extern-v128.exe + ${OTOOL} -tV AdrpLdrGotLdrField-extern-v128.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-extern-v128.exe | grep 'ldr\s*x1,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-extern-v128.exe | grep 'ldr\s*q2, \[x1, #16\]' | ${FAIL_IF_EMPTY} + + +AdrpLdrGotLdrField-externfargot: main.o + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 8-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-externfargot-g8.o -DFOO_AS_CONST -DLOAD_GPR_8 -DTARGET=_malloc -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-externfargot-g8.o main.o -o AdrpLdrGotLdrField-externfargot-g8.exe + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-g8.exe | grep 'adrp' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-g8.exe | grep 'ldr\s*x1, \[x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-g8.exe | grep 'ldr\s*b2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 16-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-externfargot-g16.o -DFOO_AS_CONST -DLOAD_GPR_16 -DTARGET=_malloc -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-externfargot-g16.o main.o -o AdrpLdrGotLdrField-externfargot-g16.exe + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-g16.exe | grep 'adrp' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-g16.exe | grep 'ldr\s*x1, \[x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-g16.exe | grep 'ldr\s*h2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot farfor 32-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-externfargot-g32.o -DFOO_AS_CONST -DLOAD_GPR_32 -DTARGET=_malloc -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-externfargot-g32.o main.o -o AdrpLdrGotLdrField-externfargot-g32.exe + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-g32.exe | grep 'adrp' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-g32.exe | grep 'ldr\s*x1, \[x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-g32.exe | grep 'ldr\s*w2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 64-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-externfargot-g64.o -DFOO_AS_CONST -DLOAD_GPR_64 -DTARGET=_malloc -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-externfargot-g64.o main.o -o AdrpLdrGotLdrField-externfargot-g64.exe + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-g64.exe | grep 'adrp' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-g64.exe | grep 'ldr\s*x1, \[x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-g64.exe | grep 'ldr\s*x2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 32-bit fp load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-externfargot-f32.o -DFOO_AS_CONST -DLOAD_FPR_32 -DTARGET=_malloc -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-externfargot-f32.o main.o -o AdrpLdrGotLdrField-externfargot-f32.exe + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-f32.exe | grep 'adrp' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-f32.exe | grep 'ldr\s*x1, \[x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-f32.exe | grep 'ldr\s*s2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 64-bit fp load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-externfargot-f64.o -DFOO_AS_CONST -DLOAD_FPR_64 -DTARGET=_malloc -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-externfargot-f64.o main.o -o AdrpLdrGotLdrField-externfargot-f64.exe + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-f64.exe | grep 'adrp' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-f64.exe | grep 'ldr\s*x1, \[x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-f64.exe | grep 'ldr\s*d2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -left untouched when target is external and GOT slot far for 128-bit vec load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-externfargot-v128.o -DFOO_AS_CONST -DLOAD_VEC_128 -DTARGET=_malloc -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-externfargot-v128.o main.o -o AdrpLdrGotLdrField-externfargot-v128.exe + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-v128.exe | grep 'adrp' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-v128.exe | grep 'ldr\s*x1, \[x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-externfargot-v128.exe | grep 'ldr\s*q2, \[x1, #16\]' | ${FAIL_IF_EMPTY} + + +AdrpLdrGotLdrField-near: main.o + # test ADRP/LDR left untouched when target is in __TEXT for 8-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-near-g8.o -DFOO_AS_CONST -DLOAD_GPR_8 + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-near-g8.o main.o -o AdrpLdrGotLdrField-near-g8.exe + ${OTOOL} -tV AdrpLdrGotLdrField-near-g8.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-g8.exe | grep 'adr\s*x1,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-near-g8.exe | grep 'ldr\s*b2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 16-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-near-g16.o -DFOO_AS_CONST -DLOAD_GPR_16 + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-near-g16.o main.o -o AdrpLdrGotLdrField-near-g16.exe + ${OTOOL} -tV AdrpLdrGotLdrField-near-g16.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-g16.exe | grep 'adr\s*x1,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-near-g16.exe | grep 'ldr\s*h2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -> LDR literal when target close for 32-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-near-g32.o -DFOO_AS_CONST -DLOAD_GPR_32 + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-near-g32.o main.o -o AdrpLdrGotLdrField-near-g32.exe + ${OTOOL} -tV AdrpLdrGotLdrField-near-g32.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-g32.exe | grep 'ldr\s*x1, \[x0' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-g32.exe | grep 'ldr\s*w2, _foo' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-g32.exe | grep 'ldr\s*w2,' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 64-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-near-g64.o -DFOO_AS_CONST -DLOAD_GPR_64 + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-near-g64.o main.o -o AdrpLdrGotLdrField-near-g64.exe + ${OTOOL} -tV AdrpLdrGotLdrField-near-g64.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-g64.exe | grep 'ldr\s*x1, \[x0' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-g64.exe | grep 'ldr\s*x2, _foo' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-g64.exe | grep 'ldr\s*x2,' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 32-bit fp load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-near-f32.o -DFOO_AS_CONST -DLOAD_FPR_32 + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-near-f32.o main.o -o AdrpLdrGotLdrField-near-f32.exe + ${OTOOL} -tV AdrpLdrGotLdrField-near-f32.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-f32.exe | grep 'ldr\s*x1, \[x0' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-f32.exe | grep 'ldr\s*s2, _foo' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-f32.exe | grep 'ldr\s*s2,' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 64-bit fp load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-near-f64.o -DFOO_AS_CONST -DLOAD_FPR_64 + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-near-f64.o main.o -o AdrpLdrGotLdrField-near-f64.exe + ${OTOOL} -tV AdrpLdrGotLdrField-near-f64.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-f64.exe | grep 'ldr\s*x1, \[x0' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-f64.exe | grep 'ldr\s*d2, _foo' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-f64.exe | grep 'ldr\s*d2,' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -left untouched when target is external and GOT slot far for 128-bit vec load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-near-v128.o -DFOO_AS_CONST -DLOAD_VEC_128 + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-near-v128.o main.o -o AdrpLdrGotLdrField-near-v128.exe + ${OTOOL} -tV AdrpLdrGotLdrField-near-v128.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-v128.exe | grep 'ldr\s*x1, \[x0' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-v128.exe | grep 'ldr\s*q2, _foo' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-near-v128.exe | grep 'ldr\s*q2,' | ${FAIL_IF_EMPTY} + + +AdrpLdrGotLdrField-nearunaligned: main.o + # test ADRP/LDR/LDR -> ADR/LDR when target in __TEXT for 8-bit unaligned load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-nearunaligned-g8.o -DFOO_AS_CONST -DLOAD_GPR_8 -DMISALIGN_DATA + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-nearunaligned-g8.o main.o -o AdrpLdrGotLdrField-nearunaligned-g8.exe + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-g8.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-g8.exe | grep 'adr\s*x1,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-g8.exe | grep 'ldr\s*b2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -> ADR/LDR when target in __TEXT for 16-bit unaligned load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-nearunaligned-g16.o -DFOO_AS_CONST -DLOAD_GPR_16 -DMISALIGN_DATA + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-nearunaligned-g16.o main.o -o AdrpLdrGotLdrField-nearunaligned-g16.exe + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-g16.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-g16.exe | grep 'adr\s*x1,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-g16.exe | grep 'ldr\s*h2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -> ADR/LDR when target in __TEXT for 32-bit unaligned load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-nearunaligned-g32.o -DFOO_AS_CONST -DLOAD_GPR_32 -DMISALIGN_DATA + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-nearunaligned-g32.o main.o -o AdrpLdrGotLdrField-nearunaligned-g32.exe + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-g32.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-g32.exe | grep 'adr\s*x1,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-g32.exe | grep 'ldr\s*w2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -> ADR/LDR when target in __TEXT for 64-bit unaligned load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-nearunaligned-g64.o -DFOO_AS_CONST -DLOAD_GPR_64 -DMISALIGN_DATA + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-nearunaligned-g64.o main.o -o AdrpLdrGotLdrField-nearunaligned-g64.exe + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-g64.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-g64.exe | grep 'adr\s*x1,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-g64.exe | grep 'ldr\s*x2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -> ADR/LDR when target in __TEXT for 32-bit FP unaligned load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-nearunaligned-f32.o -DFOO_AS_CONST -DLOAD_FPR_32 -DMISALIGN_DATA + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-nearunaligned-f32.o main.o -o AdrpLdrGotLdrField-nearunaligned-f32.exe + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-f32.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-f32.exe | grep 'adr\s*x1,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-f32.exe | grep 'ldr\s*s2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -> ADR/LDR when target in __TEXT for 64-bit FP unaligned load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-nearunaligned-f64.o -DFOO_AS_CONST -DLOAD_FPR_64 -DMISALIGN_DATA + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-nearunaligned-f64.o main.o -o AdrpLdrGotLdrField-nearunaligned-f64.exe + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-f64.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-f64.exe | grep 'adr\s*x1,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-f64.exe | grep 'ldr\s*d2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -> ADR/LDR when target in __TEXT for 12-bit vec unaligned load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-nearunaligned-v128.o -DFOO_AS_CONST -DLOAD_VEC_128 -DMISALIGN_DATA + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-nearunaligned-v128.o main.o -o AdrpLdrGotLdrField-nearunaligned-v128.exe + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-v128.exe | grep 'adrp' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-v128.exe | grep 'adr\s*x1,' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-nearunaligned-v128.exe | grep 'ldr\s*q2, \[x1, #16\]' | ${FAIL_IF_EMPTY} + + +AdrpLdrGotLdrField-far: main.o + # test ADRP/LDR left untouched when target is in __TEXT for 8-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-far-g8.o -DFOO_AS_CONST -DLOAD_GPR_8 -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-far-g8.o main.o -o AdrpLdrGotLdrField-far-g8.exe + ${OTOOL} -tV AdrpLdrGotLdrField-far-g8.exe | grep 'adrp\s*x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-far-g8.exe | grep 'add\t' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-far-g8.exe | grep 'ldr\s*b2, \[x0,' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 16-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-far-g16.o -DFOO_AS_CONST -DLOAD_GPR_16 -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-far-g16.o main.o -o AdrpLdrGotLdrField-far-g16.exe + ${OTOOL} -tV AdrpLdrGotLdrField-far-g16.exe | grep 'adrp\s*x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-far-g16.exe | grep 'add\t' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-far-g16.exe | grep 'ldr\s*h2, \[x0,' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -> LDR literal when target close for 32-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-far-g32.o -DFOO_AS_CONST -DLOAD_GPR_32 -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-far-g32.o main.o -o AdrpLdrGotLdrField-far-g32.exe + ${OTOOL} -tV AdrpLdrGotLdrField-far-g32.exe | grep 'adrp\s*x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-far-g32.exe | grep 'add\t' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-far-g32.exe | grep 'ldr\s*w2, \[x0,' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 64-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-far-g64.o -DFOO_AS_CONST -DLOAD_GPR_64 -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-far-g64.o main.o -o AdrpLdrGotLdrField-far-g64.exe + ${OTOOL} -tV AdrpLdrGotLdrField-far-g64.exe | grep 'adrp\s*x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-far-g64.exe | grep 'add\t' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-far-g64.exe | grep 'ldr\s*x2, \[x0,' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 32-bit fp load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-far-f32.o -DFOO_AS_CONST -DLOAD_FPR_32 -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-far-f32.o main.o -o AdrpLdrGotLdrField-far-f32.exe + ${OTOOL} -tV AdrpLdrGotLdrField-far-f32.exe | grep 'adrp\s*x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-far-f32.exe | grep 'add\t' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-far-f32.exe | grep 'ldr\s*s2, \[x0,' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 64-bit fp load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-far-f64.o -DFOO_AS_CONST -DLOAD_FPR_64 -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-far-f64.o main.o -o AdrpLdrGotLdrField-far-f64.exe + ${OTOOL} -tV AdrpLdrGotLdrField-far-f64.exe | grep 'adrp\s*x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-far-f64.exe | grep 'add\t' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-far-f64.exe | grep 'ldr\s*d2, \[x0,' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 128-bit vec load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-far-v128.o -DFOO_AS_CONST -DLOAD_VEC_128 -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-far-v128.o main.o -o AdrpLdrGotLdrField-far-v128.exe + ${OTOOL} -tV AdrpLdrGotLdrField-far-v128.exe | grep 'adrp\s*x0' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-far-v128.exe | grep 'add\t' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-far-v128.exe | grep 'ldr\s*q2, \[x0,' | ${FAIL_IF_EMPTY} + + +AdrpLdrGotLdrField-farunaligned: main.o + # test ADRP/LDR left untouched when target is in __TEXT for 8-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-farunaligned-g8.o -DFOO_AS_CONST -DLOAD_GPR_8 -DMISALIGN_DATA -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-farunaligned-g8.o main.o -o AdrpLdrGotLdrField-farunaligned-g8.exe + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-g8.exe | grep 'adrp' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-g8.exe | grep 'add\t' | ${FAIL_IF_STDIN} + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-g8.exe | grep 'ldr\s*b2, \[x0,' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 16-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-farunaligned-g16.o -DFOO_AS_CONST -DLOAD_GPR_16 -DMISALIGN_DATA -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-farunaligned-g16.o main.o -o AdrpLdrGotLdrField-farunaligned-g16.exe + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-g16.exe | grep 'adrp' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-g16.exe | grep 'add\t' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-g16.exe | grep 'ldr\s*h2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -> LDR literal when target close for 32-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-farunaligned-g32.o -DFOO_AS_CONST -DLOAD_GPR_32 -DMISALIGN_DATA -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-farunaligned-g32.o main.o -o AdrpLdrGotLdrField-farunaligned-g32.exe + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-g32.exe | grep 'adrp' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-g32.exe | grep 'add\t' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-g32.exe | grep 'ldr\s*w2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 64-bit load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-farunaligned-g64.o -DFOO_AS_CONST -DLOAD_GPR_64 -DMISALIGN_DATA -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-farunaligned-g64.o main.o -o AdrpLdrGotLdrField-farunaligned-g64.exe + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-g64.exe | grep 'adrp' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-g64.exe | grep 'add\t' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-g64.exe | grep 'ldr\s*x2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 32-bit fp load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-farunaligned-f32.o -DFOO_AS_CONST -DLOAD_FPR_32 -DMISALIGN_DATA -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-farunaligned-f32.o main.o -o AdrpLdrGotLdrField-farunaligned-f32.exe + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-f32.exe | grep 'adrp' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-f32.exe | grep 'add\t' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-f32.exe | grep 'ldr\s*s2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR left untouched when target is external and GOT slot far for 64-bit fp load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-farunaligned-f64.o -DFOO_AS_CONST -DLOAD_FPR_64 -DMISALIGN_DATA -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-farunaligned-f64.o main.o -o AdrpLdrGotLdrField-farunaligned-f64.exe + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-f64.exe | grep 'adrp' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-f64.exe | grep 'add\t' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-f64.exe | grep 'ldr\s*d2, \[x1, #8\]' | ${FAIL_IF_EMPTY} + + # test ADRP/LDR/LDR -left untouched when target is external and GOT slot far for 128-bit vec load + ${CC} ${CCFLAGS} AdrpLdrGotLdrField.s -c -o AdrpLdrGotLdrField-farunaligned-v128.o -DFOO_AS_CONST -DLOAD_VEC_128 -DMISALIGN_DATA -DPADDING + ${CC} ${CCFLAGS} AdrpLdrGotLdrField-farunaligned-v128.o main.o -o AdrpLdrGotLdrField-farunaligned-v128.exe + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-v128.exe | grep 'adrp' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-v128.exe | grep 'add\t' | ${FAIL_IF_EMPTY} + ${OTOOL} -tV AdrpLdrGotLdrField-farunaligned-v128.exe | grep 'ldr\s*q2, \[x1, #16\]' | ${FAIL_IF_EMPTY} + + + + AdrpAddStr: AdrpAddStr-base AdrpAddStr-seg AdrpAddStr-align AdrpAddStr-addend AdrpAddStr-faraddend true diff --git a/unit-tests/test-cases/linker_options-library-chain/Makefile b/unit-tests/test-cases/linker_options-library-chain/Makefile new file mode 100644 index 0000000..240f553 --- /dev/null +++ b/unit-tests/test-cases/linker_options-library-chain/Makefile @@ -0,0 +1,47 @@ +## +# 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@ +## +TESTROOT = ../.. +include ${TESTROOT}/include/common.makefile + +# +# main2.o has hint to link with libfoo +# libfoo.a(libfoo2.o) has link to link with libbar.dylib +# + +run: all + +all: + ${CC} ${CCFLAGS} subbar.c -dynamiclib -o libsubbar.dylib + ${CC} ${CCFLAGS} bar.c -dynamiclib -o libbar.dylib -Wl,-reexport_library,libsubbar.dylib + ${CC} ${CCFLAGS} foo.c -c -o foo.o + ${LD} -r foo.o -add_linker_option -lbar -o foo2.o + libtool -static foo2.o -o libfoo.a + ${CC} ${CCFLAGS} main.c -c -o main.o + ${LD} -r main.o -add_linker_option -lfoo -o main2.o + ${CC} ${CCFLAGS} main2.o -o main -L. + ${DYLDINFO} -lazy_bind main | grep _bar | grep libbar | ${FAIL_IF_EMPTY} + ${PASS_IFF_GOOD_MACHO} main + +clean: + rm -f libsubbar.dylib libbar.dylib foo.o foo2.o libfoo.a main.o main2.o main + diff --git a/unit-tests/test-cases/linker_options-library-chain/bar.c b/unit-tests/test-cases/linker_options-library-chain/bar.c new file mode 100644 index 0000000..981110f --- /dev/null +++ b/unit-tests/test-cases/linker_options-library-chain/bar.c @@ -0,0 +1 @@ +void bar() { } diff --git a/unit-tests/test-cases/linker_options-library-chain/foo.c b/unit-tests/test-cases/linker_options-library-chain/foo.c new file mode 100644 index 0000000..ac077b9 --- /dev/null +++ b/unit-tests/test-cases/linker_options-library-chain/foo.c @@ -0,0 +1,8 @@ + +extern void bar(); +extern void subbar(); + +void foo() { + bar(); + subbar(); +} diff --git a/unit-tests/test-cases/linker_options-library-chain/main.c b/unit-tests/test-cases/linker_options-library-chain/main.c new file mode 100644 index 0000000..4f56fe0 --- /dev/null +++ b/unit-tests/test-cases/linker_options-library-chain/main.c @@ -0,0 +1,8 @@ + +extern void foo(); + +int main() +{ + foo(); + return 0; +} diff --git a/unit-tests/test-cases/linker_options-library-chain/subbar.c b/unit-tests/test-cases/linker_options-library-chain/subbar.c new file mode 100644 index 0000000..3619a80 --- /dev/null +++ b/unit-tests/test-cases/linker_options-library-chain/subbar.c @@ -0,0 +1 @@ +void subbar() { } diff --git a/unit-tests/test-cases/objc-category-optimize-load/Makefile b/unit-tests/test-cases/objc-category-optimize-load/Makefile index 7dcafac..be39fa7 100644 --- a/unit-tests/test-cases/objc-category-optimize-load/Makefile +++ b/unit-tests/test-cases/objc-category-optimize-load/Makefile @@ -43,7 +43,7 @@ all-armv6: all-rest all-armv7: all-rest all-rest: - # check optimzation of category methods + # check optimization of category methods ${CC} ${CCFLAGS} ${OPTIONS} -dynamiclib foo.m cat1.m -framework Foundation -o libfoo.dylib size -l libfoo.dylib | grep "__objc_catlist:" | ${FAIL_IF_EMPTY} ${PASS_IFF_GOOD_MACHO} libfoo.dylib diff --git a/unit-tests/test-cases/objc-category-optimize/Makefile b/unit-tests/test-cases/objc-category-optimize/Makefile index de4ea0c..37c69f1 100644 --- a/unit-tests/test-cases/objc-category-optimize/Makefile +++ b/unit-tests/test-cases/objc-category-optimize/Makefile @@ -51,19 +51,19 @@ all-rest: ${CC} ${CCFLAGS} ${OPTIONS} -dynamiclib foo.m cat1.m cat2.m -framework Foundation -Wl,-no_objc_category_merging -o libno.dylib size -l libno.dylib | grep "__objc_catlist: 0" | ${FAIL_IF_STDIN} otool -ov libno.dylib | grep -A17 __objc_classlist | grep -A16 '_OBJC_CLASS_$$_Foo' | grep "count 1" | ${FAIL_IF_EMPTY} - # check optimzation of category methods + # check optimization of category methods ${CC} ${CCFLAGS} ${OPTIONS} -dynamiclib foo.m cat1.m cat2.m -framework Foundation -o libfoo.dylib size -l libfoo.dylib | grep "__objc_catlist: 0" | ${FAIL_IF_EMPTY} otool -ov libfoo.dylib | grep -A20 __objc_classlist | grep -A20 '_OBJC_CLASS_$$_Foo' | grep "count 4" | ${FAIL_IF_EMPTY} - # check optimzation of protocol and category methods + # check optimization of protocol and category methods ${CC} ${CCFLAGS} ${OPTIONS} -dynamiclib foo.m cat1.m cat2.m -DPROTOCOLS -framework Foundation -o libfoo2.dylib size -l libfoo2.dylib | grep "__objc_catlist: 0" | ${FAIL_IF_EMPTY} otool -ov libfoo2.dylib | grep -A20 __objc_classlist | grep -A20 '_OBJC_CLASS_$$_Foo' | grep "count 6" | ${FAIL_IF_EMPTY} - # check optimzation of properties and category methods + # check optimization of properties and category methods ${CC} ${CCFLAGS} ${OPTIONS} -dynamiclib foo.m cat1.m cat2.m -DPROPERTIES -framework Foundation -o libfoo3.dylib size -l libfoo3.dylib | grep "__objc_catlist: 0" | ${FAIL_IF_EMPTY} otool -ov libfoo3.dylib | grep -A20 __objc_classlist | grep -A20 '_OBJC_CLASS_$$_Foo' | grep "count 6" | ${FAIL_IF_EMPTY} - # check optimzation of category methods and no base methods + # check optimization of category methods and no base methods ${CC} ${CCFLAGS} ${OPTIONS} -dynamiclib foo.m cat1.m cat2.m -DNO_BASE_METHODS -framework Foundation -o libfoo4.dylib size -l libfoo4.dylib | grep "__objc_catlist: 0" | ${FAIL_IF_EMPTY} otool -ov libfoo4.dylib | grep -A20 __objc_classlist | grep -A20 '_OBJC_CLASS_$$_Foo' | grep "count 3" | ${FAIL_IF_EMPTY} -- 2.45.2