diff --git a/llvm/include/llvm/BinaryFormat/MachO.h b/llvm/include/llvm/BinaryFormat/MachO.h --- a/llvm/include/llvm/BinaryFormat/MachO.h +++ b/llvm/include/llvm/BinaryFormat/MachO.h @@ -1103,6 +1103,27 @@ uint64_t addend; }; +// The `bind` field (most significant bit) of the encoded fixup determines +// whether it is dyld_chained_ptr_64_bind or dyld_chained_ptr_64_rebase. + +// DYLD_CHAINED_PTR_64/DYLD_CHAINED_PTR_64_OFFSET +struct dyld_chained_ptr_64_bind { + uint64_t ordinal : 24; + uint64_t addend : 8; + uint64_t reserved : 19; + uint64_t next : 12; + uint64_t bind : 1; // set to 1 +}; + +// DYLD_CHAINED_PTR_64/DYLD_CHAINED_PTR_64_OFFSET +struct dyld_chained_ptr_64_rebase { + uint64_t target : 36; + uint64_t high8 : 8; + uint64_t reserved : 7; + uint64_t next : 12; + uint64_t bind : 1; // set to 0 +}; + // Byte order swapping functions for MachO structs inline void swapStruct(fat_header &mh) { diff --git a/llvm/include/llvm/Object/MachO.h b/llvm/include/llvm/Object/MachO.h --- a/llvm/include/llvm/Object/MachO.h +++ b/llvm/include/llvm/Object/MachO.h @@ -296,6 +296,19 @@ bool WeakImport; }; +struct ChainedFixupsSegment { + ChainedFixupsSegment(uint8_t SegIdx, uint32_t Offset, + const MachO::dyld_chained_starts_in_segment &Header, + std::vector &&PageStarts) + : SegIdx(SegIdx), Offset(Offset), Header(Header), + PageStarts(PageStarts){}; + + uint32_t SegIdx; + uint32_t Offset; // dyld_chained_starts_in_image::seg_info_offset[SegIdx] + MachO::dyld_chained_starts_in_segment Header; + std::vector PageStarts; // page_start[] entries, host endianness +}; + /// MachOAbstractFixupEntry is an abstract class representing a fixup in a /// MH_DYLDLINK file. Fixups generally represent rebases and binds. Binds also /// subdivide into additional subtypes (weak, lazy, reexport). @@ -364,19 +377,29 @@ class MachOChainedFixupEntry : public MachOAbstractFixupEntry { public: - enum class FixupKind { All, Bind, WeakBind, Rebase }; + enum class FixupKind { Bind, Rebase }; MachOChainedFixupEntry(Error *Err, const MachOObjectFile *O, bool Parse); bool operator==(const MachOChainedFixupEntry &) const; + bool isBind() const { return Kind == FixupKind::Bind; } + bool isRebase() const { return Kind == FixupKind::Rebase; } + void moveNext(); void moveToFirst(); void moveToEnd(); private: + void findNextPageWithFixups(); + std::vector FixupTargets; - uint32_t FixupIndex = 0; + std::vector Segments; + ArrayRef SegmentData; + FixupKind Kind; + uint32_t InfoSegIndex = 0; // Index into Segments + uint32_t PageIndex = 0; // Index into Segments[InfoSegIdx].PageStarts + uint32_t PageOffset = 0; // Page offset of the current fixup }; using fixup_iterator = content_iterator; @@ -436,6 +459,7 @@ /// Return the raw contents of an entire segment. ArrayRef getSegmentContents(StringRef SegmentName) const; + ArrayRef getSegmentContents(size_t SegmentIndex) const; /// When dsymutil generates the companion file, it strips all unnecessary /// sections (e.g. everything in the _TEXT segment) by omitting their body @@ -697,18 +721,6 @@ // upstreams their implementation. Please do not rely on this. Expected> getChainedFixupsLoadCommand() const; - struct ChainedFixupsSegment { - ChainedFixupsSegment(uint8_t SegIdx, uint32_t Offset, - const MachO::dyld_chained_starts_in_segment &Header, - std::vector &&PageStarts) - : SegIdx(SegIdx), Offset(Offset), Header(Header), - PageStarts(PageStarts){}; - - uint32_t SegIdx; - uint32_t Offset; // dyld_chained_starts_in_image::seg_info_offset[SegIdx] - MachO::dyld_chained_starts_in_segment Header; - std::vector PageStarts; // page_start[] entries, host endianness - }; // Returns the number of sections listed in dyld_chained_starts_in_image, and // a ChainedFixupsSegment for each segment that has fixups. Expected>> diff --git a/llvm/lib/Object/MachOObjectFile.cpp b/llvm/lib/Object/MachOObjectFile.cpp --- a/llvm/lib/Object/MachOObjectFile.cpp +++ b/llvm/lib/Object/MachOObjectFile.cpp @@ -2073,6 +2073,19 @@ Segment.fileoff, Segment.fileoff + Segment.filesize)); return {}; } + +template +ArrayRef getSegmentContents(const MachOObjectFile &Obj, + MachOObjectFile::LoadCommandInfo LoadCmd) { + auto SegmentOrErr = getStructOrErr(Obj, LoadCmd.Ptr); + if (!SegmentOrErr) { + consumeError(SegmentOrErr.takeError()); + return {}; + } + auto &Segment = SegmentOrErr.get(); + return arrayRefFromStringRef( + Obj.getData().slice(Segment.fileoff, Segment.fileoff + Segment.filesize)); +} } // namespace ArrayRef @@ -2097,6 +2110,28 @@ return {}; } +ArrayRef +MachOObjectFile::getSegmentContents(size_t SegmentIndex) const { + size_t Idx = 0; + for (auto LoadCmd : load_commands()) { + switch (LoadCmd.C.cmd) { + case MachO::LC_SEGMENT: + if (Idx == SegmentIndex) + return ::getSegmentContents(*this, LoadCmd); + ++Idx; + break; + case MachO::LC_SEGMENT_64: + if (Idx == SegmentIndex) + return ::getSegmentContents(*this, LoadCmd); + ++Idx; + break; + default: + continue; + } + } + return {}; +} + unsigned MachOObjectFile::getSectionID(SectionRef Sec) const { return Sec.getRawDataRefImpl().d.a; } @@ -3257,6 +3292,8 @@ void MachOAbstractFixupEntry::moveToEnd() { Done = true; } +void MachOAbstractFixupEntry::moveNext() {} + MachOChainedFixupEntry::MachOChainedFixupEntry(Error *E, const MachOObjectFile *O, bool Parse) @@ -3264,17 +3301,54 @@ ErrorAsOutParameter e(E); if (!Parse) return; - if (auto FixupTargetsOrErr = O->getDyldChainedFixupTargets()) + + if (auto FixupTargetsOrErr = O->getDyldChainedFixupTargets()) { FixupTargets = *FixupTargetsOrErr; - else { + } else { *E = FixupTargetsOrErr.takeError(); return; } + + if (auto SegmentsOrErr = O->getChainedFixupsSegments()) { + Segments = std::move(SegmentsOrErr->second); + } else { + *E = SegmentsOrErr.takeError(); + return; + } +} + +void MachOChainedFixupEntry::findNextPageWithFixups() { + auto FindInSegment = [this]() { + const ChainedFixupsSegment &SegInfo = Segments[InfoSegIndex]; + while (PageIndex < SegInfo.PageStarts.size() && + SegInfo.PageStarts[PageIndex] == MachO::DYLD_CHAINED_PTR_START_NONE) + ++PageIndex; + return PageIndex < SegInfo.PageStarts.size(); + }; + + while (InfoSegIndex < Segments.size()) { + if (FindInSegment()) { + PageOffset = Segments[InfoSegIndex].PageStarts[PageIndex]; + SegmentData = O->getSegmentContents(Segments[InfoSegIndex].SegIdx); + return; + } + + InfoSegIndex++; + PageIndex = 0; + } } void MachOChainedFixupEntry::moveToFirst() { MachOAbstractFixupEntry::moveToFirst(); - FixupIndex = 0; + if (Segments.empty()) { + Done = true; + return; + } + + InfoSegIndex = 0; + PageIndex = 0; + + findNextPageWithFixups(); moveNext(); } @@ -3282,15 +3356,104 @@ MachOAbstractFixupEntry::moveToEnd(); } -void MachOChainedFixupEntry::moveNext() { Done = true; } +void MachOChainedFixupEntry::moveNext() { + ErrorAsOutParameter ErrAsOutParam(E); + + if (InfoSegIndex == Segments.size()) { + Done = true; + return; + } + + const ChainedFixupsSegment &SegInfo = Segments[InfoSegIndex]; + SegmentIndex = SegInfo.SegIdx; + SegmentOffset = SegInfo.Header.page_size * PageIndex + PageOffset; + + // FIXME: Handle other pointer formats. + uint16_t PointerFormat = SegInfo.Header.pointer_format; + if (PointerFormat != MachO::DYLD_CHAINED_PTR_64 && + PointerFormat != MachO::DYLD_CHAINED_PTR_64_OFFSET) { + *E = createError("segment " + Twine(SegmentIndex) + + " has unsupported chained fixup pointer_format " + + Twine(PointerFormat)); + moveToEnd(); + return; + } + + Ordinal = 0; + Flags = 0; + Addend = 0; + PointerValue = 0; + SymbolName = {}; + + if (SegmentOffset + sizeof(RawValue) > SegmentData.size()) { + *E = malformedError("fixup in segment " + Twine(SegmentIndex) + + " at offset " + Twine(SegmentOffset) + + " extends past segment's end"); + moveToEnd(); + return; + } + + static_assert(sizeof(RawValue) == sizeof(MachO::dyld_chained_import_addend)); + memcpy(&RawValue, SegmentData.data() + SegmentOffset, sizeof(RawValue)); + if (O->isLittleEndian() != sys::IsLittleEndianHost) + sys::swapByteOrder(RawValue); + + // The bit extraction below assumes little-endian fixup entries. + assert(O->isLittleEndian() && "big-endian object should have been rejected " + "by getDyldChainedFixupTargets()"); + auto Field = [this](uint8_t Right, uint8_t Count) { + return (RawValue >> Right) & ((1ULL << Count) - 1); + }; + + // The `bind` field (most significant bit) of the encoded fixup determines + // whether it is dyld_chained_ptr_64_bind or dyld_chained_ptr_64_rebase. + bool IsBind = Field(63, 1); + Kind = IsBind ? FixupKind::Bind : FixupKind::Rebase; + uint32_t Next = Field(51, 12); + if (IsBind) { + uint32_t ImportOrdinal = Field(0, 24); + uint8_t InlineAddend = Field(24, 8); + + if (ImportOrdinal >= FixupTargets.size()) { + *E = malformedError("fixup in segment " + Twine(SegmentIndex) + + " at offset " + Twine(SegmentOffset) + + " has out-of range import ordinal " + + Twine(ImportOrdinal)); + moveToEnd(); + return; + } + + ChainedFixupTarget &Target = FixupTargets[ImportOrdinal]; + Ordinal = Target.libOrdinal(); + Addend = InlineAddend ? InlineAddend : Target.addend(); + Flags = Target.weakImport() ? MachO::BIND_SYMBOL_FLAGS_WEAK_IMPORT : 0; + SymbolName = Target.symbolName(); + } else { + uint64_t Target = Field(0, 36); + uint64_t High8 = Field(36, 8); + + PointerValue = Target | (High8 << 56); + if (PointerFormat == MachO::DYLD_CHAINED_PTR_64_OFFSET) + PointerValue += textAddress(); + } + + // The stride is 4 bytes for DYLD_CHAINED_PTR_64(_OFFSET). + if (Next != 0) { + PageOffset += 4 * Next; + } else { + ++PageIndex; + findNextPageWithFixups(); + } +} bool MachOChainedFixupEntry::operator==( const MachOChainedFixupEntry &Other) const { - if (Done == Other.Done) - return true; - if ((FixupIndex == Other.FixupIndex)) + if (Done && Other.Done) return true; - return false; + if (Done != Other.Done) + return false; + return InfoSegIndex == Other.InfoSegIndex && PageIndex == Other.PageIndex && + PageOffset == Other.PageOffset; } MachORebaseEntry::MachORebaseEntry(Error *E, const MachOObjectFile *O, @@ -4302,6 +4465,9 @@ } iterator_range MachOObjectFile::fixupTable(Error &Err) { + if (BindRebaseSectionTable == nullptr) + BindRebaseSectionTable = std::make_unique(this); + MachOChainedFixupEntry Start(&Err, this, true); Start.moveToFirst(); @@ -4836,13 +5002,13 @@ return CFHeader; } -Expected>> +Expected>> MachOObjectFile::getChainedFixupsSegments() const { auto CFOrErr = getChainedFixupsLoadCommand(); if (!CFOrErr) return CFOrErr.takeError(); - std::vector Segments; + std::vector Segments; if (!CFOrErr->has_value()) return std::make_pair(0, Segments); diff --git a/llvm/test/tools/llvm-objdump/MachO/dyld-info.test b/llvm/test/tools/llvm-objdump/MachO/dyld-info.test --- a/llvm/test/tools/llvm-objdump/MachO/dyld-info.test +++ b/llvm/test/tools/llvm-objdump/MachO/dyld-info.test @@ -1,9 +1,22 @@ -RUN: llvm-objdump --macho --dyld-info %p/Inputs/bind.macho-x86_64 \ -RUN: | FileCheck %s --match-full-lines --strict-whitespace \ -RUN: --implicit-check-not={{.}} -RUN: llvm-otool -dyld_info %p/Inputs/bind.macho-x86_64 \ -RUN: | FileCheck %s --match-full-lines --strict-whitespace \ -RUN: --implicit-check-not={{.}} +RUN: llvm-objdump --macho --dyld-info %p/Inputs/chained-fixups.macho-x86_64 | \ +RUN: FileCheck -DNAME=%p/Inputs/chained-fixups.macho-x86_64 %s +RUN: llvm-otool -dyld_info %p/Inputs/chained-fixups.macho-x86_64 | \ +RUN: FileCheck -DNAME=%p/Inputs/chained-fixups.macho-x86_64 %s -CHECK:{{.*}}bind.macho-x86_64: -CHECK:dyld information: + +## See chained-fixups.test for how the test input was generated. +CHECK: [[NAME]]: +CHECK-NEXT: dyld information: +CHECK-NEXT: segment section address pointer type addend dylib symbol/vm address +CHECK-NEXT: __DATA_CONST __const 0x3E0 0x8010000000000001 bind 0x0 libdylib _weakImport (weak import) +CHECK-NEXT: __DATA_CONST __const 0x3E8 0x8000000000000000 bind 0x0 flat-namespace _dynamicLookup +CHECK-NEXT: __DATA __data 0x3F0 0x00200000000003F0 rebase 0x3F0 +CHECK-NEXT: __DATA __data 0x400 0x8000000000000004 bind 0x0 weak _weak +CHECK-NEXT: __DATA __data 0x1410 0x8000000000000003 bind 0x0 weak _weakLocal +CHECK-NEXT: __DATA __data 0x3410 0x8010000000000002 bind 0x0 libdylib _dylib +CHECK-NEXT: __DATA __data 0x3418 0x800000002A000002 bind 0x2A libdylib _dylib + +## TODO: Print opcode-based fixups/binds as well +## For now, test that execution doesn't fail if the input uses those. +RUN: llvm-objdump --macho --dyld-info %p/Inputs/bind.macho-x86_64 +RUN: llvm-objdump --macho --dyld-info %p/Inputs/bind.macho-x86_64 diff --git a/llvm/tools/llvm-objdump/MachODump.cpp b/llvm/tools/llvm-objdump/MachODump.cpp --- a/llvm/tools/llvm-objdump/MachODump.cpp +++ b/llvm/tools/llvm-objdump/MachODump.cpp @@ -1188,15 +1188,6 @@ } } -static void printMachOChainedFixups(object::MachOObjectFile *Obj) { - Error Err = Error::success(); - for (const object::MachOChainedFixupEntry &Entry : Obj->fixupTable(Err)) { - (void)Entry; - } - if (Err) - reportError(std::move(Err), Obj->getFileName()); -} - static SmallVector GetSegmentNames(object::MachOObjectFile *O) { SmallVector Ret; for (const MachOObjectFile::LoadCommandInfo &Command : O->load_commands()) { @@ -1255,9 +1246,8 @@ "DYLD_CHAINED_PTR_ARM64E_USERLAND24", }; -static void -PrintChainedFixupsSegment(const MachOObjectFile::ChainedFixupsSegment &Segment, - StringRef SegName) { +static void PrintChainedFixupsSegment(const ChainedFixupsSegment &Segment, + StringRef SegName) { outs() << "chained starts in segment " << Segment.SegIdx << " (" << SegName << ")\n"; outs() << " size = " << Segment.Header.size << '\n'; @@ -1333,7 +1323,7 @@ << SegNames[I] << ")\n"; } - for (const MachOObjectFile::ChainedFixupsSegment &S : Segments) + for (const ChainedFixupsSegment &S : Segments) PrintChainedFixupsSegment(S, SegNames[S.SegIdx]); auto FixupTargets = @@ -1345,8 +1335,62 @@ } static void PrintDyldInfo(MachOObjectFile *O) { - outs() << "dyld information:" << '\n'; - printMachOChainedFixups(O); + Error Err = Error::success(); + + size_t SegmentWidth = strlen("segment"); + size_t SectionWidth = strlen("section"); + size_t AddressWidth = strlen("address"); + size_t AddendWidth = strlen("addend"); + size_t DylibWidth = strlen("dylib"); + const size_t PointerWidth = 2 + O->getBytesInAddress() * 2; + + auto HexLength = [](uint64_t Num) { + return Num ? (size_t)divideCeil(Log2_64(Num), 4) : 1; + }; + for (const object::MachOChainedFixupEntry &Entry : O->fixupTable(Err)) { + SegmentWidth = std::max(SegmentWidth, Entry.segmentName().size()); + SectionWidth = std::max(SectionWidth, Entry.sectionName().size()); + AddressWidth = std::max(AddressWidth, HexLength(Entry.address()) + 2); + if (Entry.isBind()) { + AddendWidth = std::max(AddendWidth, HexLength(Entry.addend()) + 2); + DylibWidth = std::max(DylibWidth, Entry.symbolName().size()); + } + } + // Errors will be handled when printing the table. + if (Err) + consumeError(std::move(Err)); + + outs() << "dyld information:\n"; + outs() << left_justify("segment", SegmentWidth) << ' ' + << left_justify("section", SectionWidth) << ' ' + << left_justify("address", AddressWidth) << ' ' + << left_justify("pointer", PointerWidth) << " type " + << left_justify("addend", AddendWidth) << ' ' + << left_justify("dylib", DylibWidth) << " symbol/vm address\n"; + for (const object::MachOChainedFixupEntry &Entry : O->fixupTable(Err)) { + outs() << left_justify(Entry.segmentName(), SegmentWidth) << ' ' + << left_justify(Entry.sectionName(), SectionWidth) << ' ' << "0x" + << left_justify(utohexstr(Entry.address()), AddressWidth - 2) << ' ' + << format_hex(Entry.rawValue(), PointerWidth, true) << ' '; + if (Entry.isBind()) { + outs() << "bind " + << "0x" << left_justify(utohexstr(Entry.addend()), AddendWidth - 2) + << ' ' << left_justify(ordinalName(O, Entry.ordinal()), DylibWidth) + << ' ' << Entry.symbolName(); + if (Entry.flags() & MachO::BIND_SYMBOL_FLAGS_WEAK_IMPORT) + outs() << " (weak import)"; + outs() << '\n'; + } else { + assert(Entry.isRebase()); + outs() << "rebase"; + outs().indent(AddendWidth + DylibWidth + 2); + outs() << format("0x%" PRIX64, Entry.pointerValue()) << '\n'; + } + } + if (Err) + reportError(std::move(Err), O->getFileName()); + + // TODO: Print opcode-based fixups if the object uses those. } static void PrintDylibs(MachOObjectFile *O, bool JustId) {