diff options
9 files changed, 184 insertions, 32 deletions
diff --git a/lld/lib/ReaderWriter/MachO/File.h b/lld/lib/ReaderWriter/MachO/File.h index f282d5da204..a0d20ea540f 100644 --- a/lld/lib/ReaderWriter/MachO/File.h +++ b/lld/lib/ReaderWriter/MachO/File.h @@ -201,6 +201,16 @@ public: _objcConstraint = v; } + uint32_t minVersion() const { return _minVersion; } + void setMinVersion(uint32_t v) { _minVersion = v; } + + LoadCommandType minVersionLoadCommandKind() const { + return _minVersionLoadCommandKind; + } + void setMinVersionLoadCommandKind(LoadCommandType v) { + _minVersionLoadCommandKind = v; + } + uint32_t swiftVersion() const { return _swiftVersion; } void setSwiftVersion(uint32_t v) { _swiftVersion = v; } @@ -249,6 +259,8 @@ private: NameToAtom _undefAtoms; MachOLinkingContext::Arch _arch = MachOLinkingContext::arch_unknown; MachOLinkingContext::OS _os = MachOLinkingContext::OS::unknown; + uint32_t _minVersion = 0; + LoadCommandType _minVersionLoadCommandKind = (LoadCommandType)0; MachOLinkingContext::ObjCConstraint _objcConstraint = MachOLinkingContext::objc_unknown; uint32_t _swiftVersion = 0; diff --git a/lld/lib/ReaderWriter/MachO/MachONormalizedFile.h b/lld/lib/ReaderWriter/MachO/MachONormalizedFile.h index d2b4806565d..6dab8babe65 100644 --- a/lld/lib/ReaderWriter/MachO/MachONormalizedFile.h +++ b/lld/lib/ReaderWriter/MachO/MachONormalizedFile.h @@ -253,6 +253,7 @@ struct NormalizedFile { Hex64 sourceVersion = 0; PackedVersion minOSverson = 0; PackedVersion sdkVersion = 0; + LoadCommandType minOSVersionKind = (LoadCommandType)0; // Maps to load commands with LINKEDIT content (final linked images only). Hex32 pageSize = 0; diff --git a/lld/lib/ReaderWriter/MachO/MachONormalizedFileBinaryReader.cpp b/lld/lib/ReaderWriter/MachO/MachONormalizedFileBinaryReader.cpp index 1013d3ddaef..7a9dd4b7fb9 100644 --- a/lld/lib/ReaderWriter/MachO/MachONormalizedFileBinaryReader.cpp +++ b/lld/lib/ReaderWriter/MachO/MachONormalizedFileBinaryReader.cpp @@ -471,6 +471,15 @@ readBinary(std::unique_ptr<MemoryBuffer> &mb, case LC_DYLD_INFO_ONLY: dyldInfo = reinterpret_cast<const dyld_info_command*>(lc); break; + case LC_VERSION_MIN_MACOSX: + case LC_VERSION_MIN_IPHONEOS: + case LC_VERSION_MIN_WATCHOS: + case LC_VERSION_MIN_TVOS: + // If we are emitting an object file, then we may take the load command + // kind from these commands and pass it on to the output + // file. + f->minOSVersionKind = (LoadCommandType)cmd; + break; } return false; }); diff --git a/lld/lib/ReaderWriter/MachO/MachONormalizedFileBinaryWriter.cpp b/lld/lib/ReaderWriter/MachO/MachONormalizedFileBinaryWriter.cpp index d46cf2ce386..0eb2b11eaaa 100644 --- a/lld/lib/ReaderWriter/MachO/MachONormalizedFileBinaryWriter.cpp +++ b/lld/lib/ReaderWriter/MachO/MachONormalizedFileBinaryWriter.cpp @@ -317,6 +317,10 @@ MachOFileLayout::MachOFileLayout(const NormalizedFile &file) + file.sections.size() * sectsSize + sizeof(symtab_command); _countOfLoadCommands = 2; + if (file.hasMinVersionLoadCommand) { + _endOfLoadCommands += sizeof(version_min_command); + _countOfLoadCommands++; + } if (!_file.dataInCode.empty()) { _endOfLoadCommands += sizeof(linkedit_data_command); _countOfLoadCommands++; @@ -738,6 +742,38 @@ std::error_code MachOFileLayout::writeSegmentLoadCommands(uint8_t *&lc) { return std::error_code(); } +static void writeVersionMinLoadCommand(const NormalizedFile &_file, + bool _swap, + uint8_t *&lc) { + if (!_file.hasMinVersionLoadCommand) + return; + version_min_command *vm = reinterpret_cast<version_min_command*>(lc); + switch (_file.os) { + case MachOLinkingContext::OS::unknown: + vm->cmd = _file.minOSVersionKind; + vm->cmdsize = sizeof(version_min_command); + vm->version = _file.minOSverson; + vm->sdk = 0; + break; + case MachOLinkingContext::OS::macOSX: + vm->cmd = LC_VERSION_MIN_MACOSX; + vm->cmdsize = sizeof(version_min_command); + vm->version = _file.minOSverson; + vm->sdk = _file.sdkVersion; + break; + case MachOLinkingContext::OS::iOS: + case MachOLinkingContext::OS::iOS_simulator: + vm->cmd = LC_VERSION_MIN_IPHONEOS; + vm->cmdsize = sizeof(version_min_command); + vm->version = _file.minOSverson; + vm->sdk = _file.sdkVersion; + break; + } + if (_swap) + swapStruct(*vm); + lc += sizeof(version_min_command); +} + std::error_code MachOFileLayout::writeLoadCommands() { std::error_code ec; uint8_t *lc = &_buffer[_startOfLoadCommands]; @@ -759,6 +795,11 @@ std::error_code MachOFileLayout::writeLoadCommands() { if (_swap) swapStruct(*st); lc += sizeof(symtab_command); + + // Add LC_VERSION_MIN_MACOSX, LC_VERSION_MIN_IPHONEOS, + // LC_VERSION_MIN_WATCHOS, LC_VERSION_MIN_TVOS + writeVersionMinLoadCommand(_file, _swap, lc); + // Add LC_DATA_IN_CODE if needed. if (_dataInCodeSize != 0) { linkedit_data_command* dl = reinterpret_cast<linkedit_data_command*>(lc); @@ -872,32 +913,7 @@ std::error_code MachOFileLayout::writeLoadCommands() { // Add LC_VERSION_MIN_MACOSX, LC_VERSION_MIN_IPHONEOS, LC_VERSION_MIN_WATCHOS, // LC_VERSION_MIN_TVOS - if (_file.hasMinVersionLoadCommand) { - version_min_command *vm = reinterpret_cast<version_min_command*>(lc); - switch (_file.os) { - case MachOLinkingContext::OS::unknown: - // TODO: We need to emit the load command if we managed to derive - // a platform from one of the files we are linking. - llvm_unreachable("Version commands for unknown OS aren't supported"); - break; - case MachOLinkingContext::OS::macOSX: - vm->cmd = LC_VERSION_MIN_MACOSX; - vm->cmdsize = sizeof(version_min_command); - vm->version = _file.minOSverson; - vm->sdk = _file.sdkVersion; - break; - case MachOLinkingContext::OS::iOS: - case MachOLinkingContext::OS::iOS_simulator: - vm->cmd = LC_VERSION_MIN_MACOSX; - vm->cmdsize = sizeof(version_min_command); - vm->version = _file.minOSverson; - vm->sdk = _file.sdkVersion; - break; - } - if (_swap) - swapStruct(*vm); - lc += sizeof(version_min_command); - } + writeVersionMinLoadCommand(_file, _swap, lc); // If main executable, add LC_MAIN. if (_file.fileType == llvm::MachO::MH_EXECUTE) { diff --git a/lld/lib/ReaderWriter/MachO/MachONormalizedFileFromAtoms.cpp b/lld/lib/ReaderWriter/MachO/MachONormalizedFileFromAtoms.cpp index a2b33796b8f..087b557bf8d 100644 --- a/lld/lib/ReaderWriter/MachO/MachONormalizedFileFromAtoms.cpp +++ b/lld/lib/ReaderWriter/MachO/MachONormalizedFileFromAtoms.cpp @@ -128,6 +128,18 @@ public: void copyEntryPointAddress(NormalizedFile &file); void copySectionContent(NormalizedFile &file); + bool allSourceFilesHaveMinVersions() const { + return _allSourceFilesHaveMinVersions; + } + + uint32_t minVersion() const { + return _minVersion; + } + + LoadCommandType minVersionCommandType() const { + return _minVersionCommandType; + } + private: typedef std::map<DefinedAtom::ContentType, SectionInfo*> TypeToSection; typedef llvm::DenseMap<const Atom*, uint64_t> AtomToAddress; @@ -183,6 +195,9 @@ private: std::vector<const Atom *> _machHeaderAliasAtoms; bool _hasTLVDescriptors; bool _subsectionsViaSymbols; + bool _allSourceFilesHaveMinVersions = true; + LoadCommandType _minVersionCommandType = (LoadCommandType)0; + uint32_t _minVersion = 0; }; Util::~Util() { @@ -378,11 +393,25 @@ void Util::processDefinedAtoms(const lld::File &atomFile) { } void Util::processAtomAttributes(const DefinedAtom *atom) { - // If the file doesn't use subsections via symbols, then make sure we don't - // add that flag to the final output file if we have a relocatable file. - if (auto *machoFile = dyn_cast<mach_o::MachOFile>(&atom->file())) + if (auto *machoFile = dyn_cast<mach_o::MachOFile>(&atom->file())) { + // If the file doesn't use subsections via symbols, then make sure we don't + // add that flag to the final output file if we have a relocatable file. if (!machoFile->subsectionsViaSymbols()) _subsectionsViaSymbols = false; + + // All the source files must have min versions for us to output an object + // file with a min version. + if (auto v = machoFile->minVersion()) + _minVersion = std::max(_minVersion, v); + else + _allSourceFilesHaveMinVersions = false; + + // If we don't have a platform load command, but one of the source files + // does, then take the one from the file. + if (!_minVersionCommandType) + if (auto v = machoFile->minVersionLoadCommandKind()) + _minVersionCommandType = v; + } } void Util::assignAtomToSection(const DefinedAtom *atom) { @@ -1245,14 +1274,31 @@ normalizedFromAtoms(const lld::File &atomFile, normFile.currentVersion = context.currentVersion(); normFile.compatVersion = context.compatibilityVersion(); normFile.os = context.os(); - normFile.minOSverson = context.osMinVersion(); - // FIXME: We need to get the SDK version from the system. For now the min - // OS version is better than nothing. + + // If we are emitting an object file, then the min version is the maximum + // of the min's of all the source files and the cmdline. + if (normFile.fileType == llvm::MachO::MH_OBJECT) + normFile.minOSverson = std::max(context.osMinVersion(), util.minVersion()); + else + normFile.minOSverson = context.osMinVersion(); + + normFile.minOSVersionKind = util.minVersionCommandType(); + normFile.sdkVersion = context.sdkVersion(); if (context.generateVersionLoadCommand() && context.os() != MachOLinkingContext::OS::unknown) normFile.hasMinVersionLoadCommand = true; + else if (normFile.fileType == llvm::MachO::MH_OBJECT && + util.allSourceFilesHaveMinVersions() && + ((normFile.os != MachOLinkingContext::OS::unknown) || + util.minVersionCommandType())) { + // If we emit an object file, then it should contain a min version load + // command if all of the source files also contained min version commands. + // Also, we either need to have a platform, or found a platform from the + // source object files. + normFile.hasMinVersionLoadCommand = true; + } normFile.pageSize = context.pageSize(); normFile.rpaths = context.rpaths(); util.addDependentDylibs(atomFile, normFile); diff --git a/lld/lib/ReaderWriter/MachO/MachONormalizedFileToAtoms.cpp b/lld/lib/ReaderWriter/MachO/MachONormalizedFileToAtoms.cpp index 80f2038d98b..3d4457ab912 100644 --- a/lld/lib/ReaderWriter/MachO/MachONormalizedFileToAtoms.cpp +++ b/lld/lib/ReaderWriter/MachO/MachONormalizedFileToAtoms.cpp @@ -1061,6 +1061,8 @@ normalizedObjectToAtoms(MachOFile *file, file->setFlags(normalizedFile.flags); file->setArch(normalizedFile.arch); file->setOS(normalizedFile.os); + file->setMinVersion(normalizedFile.minOSverson); + file->setMinVersionLoadCommandKind(normalizedFile.minOSVersionKind); // Sort references in each atom to their canonical order. for (const DefinedAtom* defAtom : file->defined()) { diff --git a/lld/lib/ReaderWriter/MachO/MachONormalizedFileYAML.cpp b/lld/lib/ReaderWriter/MachO/MachONormalizedFileYAML.cpp index 0b92a68eeae..c795a72530a 100644 --- a/lld/lib/ReaderWriter/MachO/MachONormalizedFileYAML.cpp +++ b/lld/lib/ReaderWriter/MachO/MachONormalizedFileYAML.cpp @@ -524,6 +524,14 @@ struct ScalarEnumerationTraits<LoadCommandType> { llvm::MachO::LC_LOAD_UPWARD_DYLIB); io.enumCase(value, "LC_LAZY_LOAD_DYLIB", llvm::MachO::LC_LAZY_LOAD_DYLIB); + io.enumCase(value, "LC_VERSION_MIN_MACOSX", + llvm::MachO::LC_VERSION_MIN_MACOSX); + io.enumCase(value, "LC_VERSION_MIN_IPHONEOS", + llvm::MachO::LC_VERSION_MIN_IPHONEOS); + io.enumCase(value, "LC_VERSION_MIN_TVOS", + llvm::MachO::LC_VERSION_MIN_TVOS); + io.enumCase(value, "LC_VERSION_MIN_WATCHOS", + llvm::MachO::LC_VERSION_MIN_WATCHOS); } }; @@ -692,6 +700,7 @@ struct MappingTraits<NormalizedFile> { io.mapOptional("source-version", file.sourceVersion, Hex64(0)); io.mapOptional("OS", file.os); io.mapOptional("min-os-version", file.minOSverson, PackedVersion(0)); + io.mapOptional("min-os-version-kind", file.minOSVersionKind, (LoadCommandType)0); io.mapOptional("sdk-version", file.sdkVersion, PackedVersion(0)); io.mapOptional("segments", file.segments); io.mapOptional("sections", file.sections); diff --git a/lld/test/mach-o/Inputs/no-version-min-load-command-object.yaml b/lld/test/mach-o/Inputs/no-version-min-load-command-object.yaml new file mode 100644 index 00000000000..35f83c6fd08 --- /dev/null +++ b/lld/test/mach-o/Inputs/no-version-min-load-command-object.yaml @@ -0,0 +1,22 @@ + +# This object file has no version min and so will prevent any -r link from emitting +# a version min. + +--- !mach-o +arch: x86_64 +file-type: MH_OBJECT +flags: [ MH_SUBSECTIONS_VIA_SYMBOLS ] +sections: + - segment: __TEXT + section: __text + type: S_REGULAR + attributes: [ S_ATTR_PURE_INSTRUCTIONS, S_ATTR_SOME_INSTRUCTIONS ] + address: 0x0000000000000000 + content: [ 0x00, 0x00, 0x00, 0x00 ] +global-symbols: + - name: _main2 + type: N_SECT + scope: [ N_EXT ] + sect: 1 + value: 0x0000000000000000 +... diff --git a/lld/test/mach-o/version-min-load-command-object.yaml b/lld/test/mach-o/version-min-load-command-object.yaml new file mode 100644 index 00000000000..33001cc6f71 --- /dev/null +++ b/lld/test/mach-o/version-min-load-command-object.yaml @@ -0,0 +1,35 @@ +# RUN: lld -flavor darwin -arch x86_64 %s -o %t -r -macosx_version_min 10.8 && llvm-objdump -private-headers %t | FileCheck %s +# RUN: lld -flavor darwin -arch x86_64 %s -o %t -r && llvm-objdump -private-headers %t | FileCheck %s +# RUN: lld -flavor darwin -arch x86_64 %s -o %t -r %p/Inputs/no-version-min-load-command-object.yaml && llvm-objdump -private-headers %t | FileCheck %s --check-prefix=NO_VERSION_MIN + +# If we are emitting an object file, then we only emit a min version load command if the source object file(s) all have +# version(s) and either known platforms or contain min version load commands themselves. + +--- !mach-o +arch: x86_64 +file-type: MH_OBJECT +flags: [ MH_SUBSECTIONS_VIA_SYMBOLS ] +min-os-version-kind: LC_VERSION_MIN_MACOSX +min-os-version: 10.8 +sections: + - segment: __TEXT + section: __text + type: S_REGULAR + attributes: [ S_ATTR_PURE_INSTRUCTIONS, S_ATTR_SOME_INSTRUCTIONS ] + address: 0x0000000000000000 + content: [ 0x00, 0x00, 0x00, 0x00 ] +global-symbols: + - name: _main + type: N_SECT + scope: [ N_EXT ] + sect: 1 + value: 0x0000000000000000 +... + +# CHECK: Load command {{[0-9]*}} +# CHECK: cmd LC_VERSION_MIN_MACOSX +# CHECK: cmdsize 16 +# CHECK: version 10.8 +# CHECK: sdk n/a + +# NO_VERSION_MIN-NOT: LC_VERSION_MIN_MACOSX
\ No newline at end of file |