From 47e4663c4eacaedab63e407651f5b045446bb36d Mon Sep 17 00:00:00 2001 From: Daniel Bertalan Date: Wed, 17 Aug 2022 16:14:03 +0200 Subject: [llvm-objdump] Add -dyld_info to llvm-otool This option outputs the location, encoded value and target of chained fixups, using the same format as `otool -dyld_info`. This initial implementation only supports the DYLD_CHAINED_PTR_64 and DYLD_CHAINED_PTR_64_OFFSET pointer encodings, which are used in x86_64 and arm64 userspace binaries. When Apple's effort to upstream their chained fixups code continues, we'll replace this code with the then-upstreamed code. But we need something in the meantime for testing ld64.lld's chained fixups code. Differential Revision: https://reviews.llvm.org/D132036 --- llvm/lib/Object/MachOObjectFile.cpp | 186 ++++++++++++++++++++++++++++++++++-- 1 file changed, 176 insertions(+), 10 deletions(-) (limited to 'llvm/lib/Object/MachOObjectFile.cpp') diff --git a/llvm/lib/Object/MachOObjectFile.cpp b/llvm/lib/Object/MachOObjectFile.cpp index 0c202b2..0b06ec3 100644 --- a/llvm/lib/Object/MachOObjectFile.cpp +++ b/llvm/lib/Object/MachOObjectFile.cpp @@ -2073,6 +2073,19 @@ ArrayRef getSegmentContents(const MachOObjectFile &Obj, 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 @@ MachOObjectFile::getSegmentContents(StringRef SegmentName) const { 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::moveToFirst() { void MachOAbstractFixupEntry::moveToEnd() { Done = true; } +void MachOAbstractFixupEntry::moveNext() {} + MachOChainedFixupEntry::MachOChainedFixupEntry(Error *E, const MachOObjectFile *O, bool Parse) @@ -3264,17 +3301,54 @@ MachOChainedFixupEntry::MachOChainedFixupEntry(Error *E, 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 @@ void MachOChainedFixupEntry::moveToEnd() { 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::weakBindTable(Error &Err) { } 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 @@ MachOObjectFile::getChainedFixupsHeader() const { 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); -- cgit v1.1