diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -1043,6 +1043,13 @@ // contrast, EH frames are handled like regular ConcatInputSections.) if (section->name == section_names::compactUnwind) continue; + + if (section->name == section_names::objcImageInfo) { + assert(section->subsections.size() == 1); + in.objCImageInfo->addInput(section->subsections[0].isec); + continue; + } + ConcatOutputSection *osec = nullptr; for (const Subsection &subsection : section->subsections) { if (auto *isec = dyn_cast(subsection.isec)) { diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h --- a/lld/MachO/SyntheticSections.h +++ b/lld/MachO/SyntheticSections.h @@ -19,6 +19,7 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/Hashing.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/SetVector.h" #include "llvm/MC/StringTableBuilder.h" #include "llvm/Support/MathExtras.h" @@ -600,6 +601,23 @@ std::unordered_map literal4Map; }; +class ObjCImageInfoSection final : public SyntheticSection { +public: + ObjCImageInfoSection(); + uint64_t getSize() const override { return 8; } + void addInput(const InputSection *isec) { inputs.push_back(isec); } + void finalizeContents(); + void writeTo(uint8_t *buf) const override; + +private: + struct ImageInfo { + uint8_t swiftVersion; + bool hasCategoryClassProperties = true; + } info; + static llvm::Optional parseImageInfo(const InputSection *isec); + std::vector inputs; +}; + struct InStruct { const uint8_t *bufferStart = nullptr; MachHeaderSection *header = nullptr; @@ -616,6 +634,7 @@ StubsSection *stubs = nullptr; StubHelperSection *stubHelper = nullptr; UnwindInfoSection *unwindInfo = nullptr; + ObjCImageInfoSection *objCImageInfo = nullptr; ConcatInputSection *imageLoaderCache = nullptr; }; diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp --- a/lld/MachO/SyntheticSections.cpp +++ b/lld/MachO/SyntheticSections.cpp @@ -1574,6 +1574,102 @@ memcpy(buf + p.second * 4, &p.first, 4); } +ObjCImageInfoSection::ObjCImageInfoSection() + : SyntheticSection(segment_names::data, section_names::objcImageInfo) {} + +Optional +ObjCImageInfoSection::parseImageInfo(const InputSection *isec) { + Optional info; + ArrayRef data = isec->data; + // The image info struct has the following layout: + // struct { + // uint32_t version; + // uint32_t flags; + // }; + if (data.size() != 8) { + warn(toString(isec->getFile()) + ": invalid __objc_imageinfo size"); + return info; + } + + auto *buf = reinterpret_cast(data.data()); + if (read32le(buf) != 0) { + warn(toString(isec->getFile()) + ": invalid __objc_imageinfo version"); + return info; + } + + info = ImageInfo{}; + uint32_t flags = read32le(buf + 1); + info->swiftVersion = (flags >> 8) & 0xff; + info->hasCategoryClassProperties = flags & 0x40; + return info; +} + +static std::string swiftVersionString(uint8_t version) { + switch (version) { + case 1: + return "1.0"; + case 2: + return "1.1"; + case 3: + return "2.0"; + case 4: + return "3.0"; + case 5: + return "4.0"; + default: + return ("0x" + Twine::utohexstr(version)).str(); + } +} + +// Validate each object file's __objc_imageinfo and use them to generate the +// image info for the output binary. Only two pieces of info are relevant: +// 1. The Swift version (should be identical across inputs) +// 2. `bool hasCategoryClassProperties` (true only if true for all inputs) +void ObjCImageInfoSection::finalizeContents() { + if (inputs.size() == 0) + return; + + // Find the first valid image info sequentially so that any error messages + // we generate are deterministic. + auto it = inputs.begin(); + InputFile *firstFile; + for (; it != inputs.end(); ++it) { + if (Optional inputInfo = parseImageInfo(*it)) { + info.swiftVersion = inputInfo->swiftVersion; + info.hasCategoryClassProperties &= inputInfo->hasCategoryClassProperties; + firstFile = (*it)->getFile(); + break; + } else { + // Invalid image infos should be treated has having + // hasCategoryClassProperties = false; + info.hasCategoryClassProperties = false; + } + } + + parallelForEach(it, inputs.end(), [&](const InputSection *isec) { + Optional inputInfo = parseImageInfo(isec); + if (!inputInfo) { + info.hasCategoryClassProperties = false; + return; + } + + if (!inputInfo->hasCategoryClassProperties) + info.hasCategoryClassProperties = false; + + if (info.swiftVersion != inputInfo->swiftVersion) + error("Swift version mismatch: " + toString(firstFile) + " has version " + + swiftVersionString(info.swiftVersion) + " but " + + toString(isec->getFile()) + " has version " + + swiftVersionString(inputInfo->swiftVersion)); + }); +} + +void ObjCImageInfoSection::writeTo(uint8_t *buf) const { + uint32_t flags = info.hasCategoryClassProperties ? 0x40 : 0x0; + flags |= info.swiftVersion << 8; + write32le(buf + 4, flags); +} + void macho::createSyntheticSymbols() { auto addHeaderSymbol = [](const char *name) { symtab->addSynthetic(name, in.header->isec, /*value=*/0, diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp --- a/lld/MachO/Writer.cpp +++ b/lld/MachO/Writer.cpp @@ -996,7 +996,7 @@ if (!osec->isNeeded()) continue; // Other kinds of OutputSections have already been finalized. - if (auto concatOsec = dyn_cast(osec)) + if (auto *concatOsec = dyn_cast(osec)) concatOsec->finalizeContents(); } } @@ -1164,6 +1164,10 @@ if (in.stubHelper->isNeeded()) in.stubHelper->setup(); + + if (in.objCImageInfo->isNeeded()) + in.objCImageInfo->finalizeContents(); + // At this point, we should know exactly which output sections are needed, // courtesy of scanSymbols() and scanRelocations(). createOutputSections(); @@ -1210,6 +1214,7 @@ in.stubs = make(); in.stubHelper = make(); in.unwindInfo = makeUnwindInfoSection(); + in.objCImageInfo = make(); // This section contains space for just a single word, and will be used by // dyld to cache an address to the image loader it uses. diff --git a/lld/test/MachO/objc-imageinfo.s b/lld/test/MachO/objc-imageinfo.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/objc-imageinfo.s @@ -0,0 +1,136 @@ +# REQUIRES: x86 + +# RUN: rm -rf %t; split-file %s %t + +## ld64 ignores the __objc_imageinfo section entirely if there is no actual +## ObjC class + category data in the file. LLD doesn't yet do this check, but +## to make this test work for both linkers, I am inserting an appropriate class +## definition into each test file. +# RUN: cat %t/objc-no-category-cls.s %t/foo-cls.s > %t/objc-no-category-cls-1.s +# RUN: cat %t/objc-with-category-cls.s %t/foo-cls.s > %t/objc-with-category-cls-1.s +# RUN: cat %t/objc-ignored-flags.s %t/foo-cls.s > %t/objc-ignored-flags-1.s +# RUN: cat %t/objc-invalid-version.s %t/foo-cls.s > %t/objc-invalid-version-1.s +# RUN: cat %t/swift-version-1.s %t/foo-cls.s > %t/swift-version-1-1.s +# RUN: cat %t/swift-version-2.s %t/foo-cls.s > %t/swift-version-2-1.s + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/objc-no-category-cls-1.s -o %t/objc-no-category-cls.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/objc-with-category-cls-1.s -o %t/objc-with-category-cls.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/objc-ignored-flags-1.s -o %t/objc-ignored-flags.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/objc-invalid-version-1.s -o %t/objc-invalid-version.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/swift-version-1-1.s -o %t/swift-version-1.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/swift-version-2-1.s -o %t/swift-version-2.o + +# RUN: %lld -dylib -lSystem %t/objc-with-category-cls.o -o %t/test-with-cat +# RUN: llvm-objdump --macho --section="__DATA_CONST,__objc_imageinfo" \ +# RUN: %t/test-with-cat | FileCheck %s --check-prefix=HAS-CAT-CLS + +# RUN: %lld -dylib -lSystem %t/objc-no-category-cls.o -o %t/test-no-cat +# RUN: llvm-objdump --macho --section="__DATA_CONST,__objc_imageinfo" \ +# RUN: %t/test-no-cat | FileCheck %s --check-prefix=NO-CAT-CLS + +# RUN: %lld -dylib -lSystem %t/objc-no-category-cls.o %t/objc-with-category-cls.o -o %t/test1 +# RUN: llvm-objdump --macho --section="__DATA_CONST,__objc_imageinfo" %t/test1 \ +# RUN: | FileCheck %s --check-prefix=NO-CAT-CLS + +# RUN: %lld -dylib -lSystem %t/objc-with-category-cls.o %t/objc-ignored-flags.o -o %t/test2 +# RUN: llvm-objdump --macho --section="__DATA_CONST,__objc_imageinfo" %t/test2 \ +# RUN: | FileCheck %s --check-prefix=HAS-CAT-CLS + +# RUN: %lld -dylib -lSystem %t/objc-no-category-cls.o %t/objc-ignored-flags.o -o %t/test3 +# RUN: llvm-objdump --macho --section="__DATA_CONST,__objc_imageinfo" %t/test3 \ +# RUN: | FileCheck %s --check-prefix=NO-CAT-CLS + +# RUN: %no-fatal-warnings-lld -dylib -lSystem %t/objc-with-category-cls.o \ +# RUN: %t/objc-invalid-version.o -o %t/test4 2>&1 | FileCheck %s \ +# RUN: --check-prefix=IMAGE-VERSION +# RUN: llvm-objdump --macho --section="__DATA_CONST,__objc_imageinfo" %t/test4 \ +# RUN: | FileCheck %s --check-prefix=NO-CAT-CLS + +# RUN: %no-fatal-warnings-lld -dylib -lSystem %t/objc-no-category-cls.o \ +# RUN: %t/objc-invalid-version.o -o %t/test5 2>&1 | FileCheck %s \ +# RUN: --check-prefix=IMAGE-VERSION +# RUN: llvm-objdump --macho --section="__DATA_CONST,__objc_imageinfo" %t/test5 \ +# RUN: | FileCheck %s --check-prefix=NO-CAT-CLS + +# RUN: not %lld -dylib -lSystem %t/swift-version-1.o %t/swift-version-2.o -o \ +# RUN: /dev/null 2>&1 | FileCheck %s --check-prefix=SWIFT-MISMATCH-12 +# RUN: not %lld -dylib -lSystem %t/swift-version-2.o %t/swift-version-1.o -o \ +# RUN: /dev/null 2>&1 | FileCheck %s --check-prefix=SWIFT-MISMATCH-21 + +# HAS-CAT-CLS: Contents of (__DATA_CONST,__objc_imageinfo) section +# HAS-CAT-CLS: 00 00 00 40 00 00 00 +# HAS-CAT-CLS-EMPTY: + +# NO-CAT-CLS: Contents of (__DATA_CONST,__objc_imageinfo) section +# NO-CAT-CLS: 00 00 00 00 00 00 00 +# NO-CAT-CLS-EMPTY: + +# IMAGE-VERSION: warning: {{.*}}objc-invalid-version.o: invalid __objc_imageinfo version + +# SWIFT-MISMATCH-12: error: Swift version mismatch: {{.*}}swift-version-1.o has version 1.0 but {{.*}}swift-version-2.o has version 1.1 +# SWIFT-MISMATCH-21: error: Swift version mismatch: {{.*}}swift-version-2.o has version 1.1 but {{.*}}swift-version-1.o has version 1.0 + +#--- objc-no-category-cls.s +.section __DATA,__objc_imageinfo,regular,no_dead_strip +.long 0 +.long 0 + +#--- objc-with-category-cls.s +.section __DATA,__objc_imageinfo,regular,no_dead_strip +.long 0 +.long 0x40 ## "has category class properties" flag + +#--- objc-ignored-flags.s +.section __DATA,__objc_imageinfo,regular,no_dead_strip +.long 0 +## Only the 0x40 flag is carried through to the output binary. +.long (0x40 | 0x20 | 0x4 | 0x2) + +#--- objc-invalid-version.s +.section __DATA,__objc_imageinfo,regular,no_dead_strip +.long 1 ## only 0 is valid; the flag field below will not be parsed. +.long 0x40 + +#--- swift-version-1.s +.section __DATA,__objc_imageinfo,regular,no_dead_strip +.long 0 +.byte 0x40 +.byte 0x1 ## Swift version +.short 0 + +#--- swift-version-2.s +.section __DATA,__objc_imageinfo,regular,no_dead_strip +.long 0 +.byte 0x40 +.byte 0x2 ## Swift version +.short 0 + +#--- foo-cls.s +.section __TEXT,__objc_classname,cstring_literals +L_CAT_NAME: +.asciz "barcat" + +.section __DATA,__objc_data +.p2align 3 +_OBJC_CLASS_$_FooClass: +.space 40 + +.section __DATA,__objc_const +.p2align 3 +__OBJC_$_CATEGORY_INSTANCE_METHODS_FooClass_$_barcat: + +.p2align 3 +__OBJC_$_CATEGORY_FooClass_$_barcat: +.quad L_CAT_NAME +.quad _OBJC_CLASS_$_FooClass +.quad __OBJC_$_CATEGORY_INSTANCE_METHODS_FooClass_$_barcat +.quad 0 +.quad 0 +.quad 0 +.quad 0 +.long 64 +.space 4 + +.section __DATA,__objc_catlist,regular,no_dead_strip +.p2align 3 +.quad __OBJC_$_CATEGORY_FooClass_$_barcat