Index: llvm/include/llvm/BinaryFormat/MachO.h =================================================================== --- llvm/include/llvm/BinaryFormat/MachO.h +++ llvm/include/llvm/BinaryFormat/MachO.h @@ -1094,6 +1094,24 @@ uint64_t addend; }; +// 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; +}; + +// 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; +}; + // Byte order swapping functions for MachO structs inline void swapStruct(fat_header &mh) { Index: llvm/include/llvm/Object/MachO.h =================================================================== --- llvm/include/llvm/Object/MachO.h +++ 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,27 @@ 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: 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 +457,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 +719,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>> Index: llvm/lib/Object/MachOObjectFile.cpp =================================================================== --- llvm/lib/Object/MachOObjectFile.cpp +++ llvm/lib/Object/MachOObjectFile.cpp @@ -2072,6 +2072,20 @@ 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)); + return {}; +} } // namespace ArrayRef @@ -2096,6 +2110,34 @@ 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) { + ArrayRef Contents = + ::getSegmentContents(*this, LoadCmd); + return Contents; + } + ++Idx; + break; + case MachO::LC_SEGMENT_64: + if (Idx == SegmentIndex) { + ArrayRef Contents = + ::getSegmentContents(*this, LoadCmd); + return Contents; + } + ++Idx; + break; + default: + continue; + } + } + return {}; +} + unsigned MachOObjectFile::getSectionID(SectionRef Sec) const { return Sec.getRawDataRefImpl().d.a; } @@ -3256,6 +3298,8 @@ void MachOAbstractFixupEntry::moveToEnd() { Done = true; } +void MachOAbstractFixupEntry::moveNext() {} + MachOChainedFixupEntry::MachOChainedFixupEntry(Error *E, const MachOObjectFile *O, bool Parse) @@ -3263,17 +3307,36 @@ 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::moveToFirst() { MachOAbstractFixupEntry::moveToFirst(); - FixupIndex = 0; + if (Segments.empty()) { + Done = true; + return; + } + + InfoSegIndex = 0; + PageIndex = 0; + while (Segments[0].PageStarts[PageIndex] == + MachO::DYLD_CHAINED_PTR_START_NONE) + ++PageIndex; + PageOffset = Segments[0].PageStarts[PageIndex]; + SegmentData = O->getSegmentContents(Segments[0].SegIdx); moveNext(); } @@ -3281,15 +3344,122 @@ 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 = PageIndex * SegInfo.Header.page_size + 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 = make_error( + "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(uint64_t)); + memcpy(&RawValue, SegmentData.data() + SegmentOffset, sizeof(RawValue)); + if (O->isLittleEndian() != sys::IsLittleEndianHost) + sys::swapByteOrder(RawValue); + + auto Field = [this](uint8_t Right, uint8_t Count) { + return (RawValue & ((1ull << Count) - 1) << Right) >> Right; + }; + + // For dyld_chained_ptr_64_{bind,rebase}, the most significant bit determines + // whether it's a bind or a 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; + return; + } + + // Find the next page that contains fixups. + ++PageIndex; + while (PageIndex < SegInfo.PageStarts.size() && + SegInfo.PageStarts[PageIndex] == MachO::DYLD_CHAINED_PTR_START_NONE) + ++PageIndex; + + if (PageIndex < SegInfo.PageStarts.size()) { + PageOffset = SegInfo.PageStarts[PageIndex]; + return; + } + + ++InfoSegIndex; + if (InfoSegIndex < Segments.size()) { + PageIndex = 0; + while (Segments[InfoSegIndex].PageStarts[PageIndex] == + MachO::DYLD_CHAINED_PTR_START_NONE) + ++PageIndex; + PageOffset = Segments[InfoSegIndex].PageStarts[PageIndex]; + SegmentData = O->getSegmentContents(Segments[InfoSegIndex].SegIdx); + return; + } +} 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, @@ -4301,6 +4471,9 @@ } iterator_range MachOObjectFile::fixupTable(Error &Err) { + if (BindRebaseSectionTable == nullptr) + BindRebaseSectionTable = std::make_unique(this); + MachOChainedFixupEntry Start(&Err, this, true); Start.moveToFirst(); @@ -4835,13 +5008,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); Index: llvm/test/tools/llvm-objdump/MachO/dyld-info.test =================================================================== --- llvm/test/tools/llvm-objdump/MachO/dyld-info.test +++ llvm/test/tools/llvm-objdump/MachO/dyld-info.test @@ -1,9 +1,18 @@ -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: + +CHECK: [[NAME]]: +CHECK-NEXT: dyld information: +CHECK-NEXT: segment section address pointer type addend dylib symbol/vm address +CHECK-NEXT: __DATA_CONST __const 0x{{0*}}3E0 0x8010000000000001 bind 0x{{0*}}0 libdylib _weakImport (weak import) +CHECK-NEXT: __DATA_CONST __const 0x{{0*}}3E8 0x8000000000000000 bind 0x{{0*}}0 flat-namespace _dynamicLookup +CHECK-NEXT: __DATA __data 0x{{0*}}3F0 0x00200000000003F0 rebase 0x{{0*}}3F0 +CHECK-NEXT: __DATA __data 0x{{0*}}400 0x8000000000000004 bind 0x{{0*}}0 weak _weak +CHECK-NEXT: __DATA __data 0x{{0*}}1410 0x8000000000000003 bind 0x{{0*}}0 weak _weakLocal +CHECK-NEXT: __DATA __data 0x{{0*}}3410 0x8010000000000002 bind 0x{{0*}}0 libdylib _dylib +CHECK-NEXT: __DATA __data 0x{{0*}}3418 0x800000002A000002 bind 0x{{0*}}2A libdylib _dylib + +## See chained-fixups.test for how the test input is generated. Index: llvm/tools/llvm-objdump/MachODump.cpp =================================================================== --- llvm/tools/llvm-objdump/MachODump.cpp +++ llvm/tools/llvm-objdump/MachODump.cpp @@ -1190,8 +1190,29 @@ static void printMachOChainedFixups(object::MachOObjectFile *Obj) { Error Err = Error::success(); + + // FIXME: Align the columns properly. + // FIXME: Support more fixup formats in MachOObjectFile. + + outs() << "segment section address pointer type " + "addend dylib symbol/vm address\n"; for (const object::MachOChainedFixupEntry &Entry : Obj->fixupTable(Err)) { - (void)Entry; + outs() << left_justify(Entry.segmentName(), 12) << " " + << left_justify(Entry.sectionName(), 12) << " " + << format_hex(Entry.address(), 10, true) << " " + << format_hex(Entry.rawValue(), 18, true) << " "; + if (Entry.isBind()) { + outs() << "bind " << format_hex(Entry.addend(), 8, true) << " " + << left_justify(ordinalName(Obj, Entry.ordinal()), 16) << " " + << Entry.symbolName(); + if (Entry.flags() & MachO::BIND_SYMBOL_FLAGS_WEAK_IMPORT) + outs() << " (weak import)"; + outs() << '\n'; + } else { + assert(Entry.isRebase()); + outs() << format("rebase %25c0x%08" PRIX64, ' ', Entry.pointerValue()) + << '\n'; + } } if (Err) reportError(std::move(Err), Obj->getFileName()); @@ -1255,9 +1276,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 +1353,7 @@ << SegNames[I] << ")\n"; } - for (const MachOObjectFile::ChainedFixupsSegment &S : Segments) + for (const ChainedFixupsSegment &S : Segments) PrintChainedFixupsSegment(S, SegNames[S.SegIdx]); auto FixupTargets =