| //=--------- MachOAtomGraphBuilder.cpp - MachO AtomGraph builder ----------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Generic MachO AtomGraph buliding code. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "MachOAtomGraphBuilder.h" |
| |
| #define DEBUG_TYPE "jitlink" |
| |
| namespace llvm { |
| namespace jitlink { |
| |
| MachOAtomGraphBuilder::~MachOAtomGraphBuilder() {} |
| |
| Expected<std::unique_ptr<AtomGraph>> MachOAtomGraphBuilder::buildGraph() { |
| if (auto Err = parseSections()) |
| return std::move(Err); |
| |
| if (auto Err = addAtoms()) |
| return std::move(Err); |
| |
| if (auto Err = addRelocations()) |
| return std::move(Err); |
| |
| return std::move(G); |
| } |
| |
| MachOAtomGraphBuilder::MachOAtomGraphBuilder(const object::MachOObjectFile &Obj) |
| : Obj(Obj), |
| G(llvm::make_unique<AtomGraph>(Obj.getFileName(), getPointerSize(Obj), |
| getEndianness(Obj))) {} |
| |
| void MachOAtomGraphBuilder::addCustomAtomizer(StringRef SectionName, |
| CustomAtomizeFunction Atomizer) { |
| assert(!CustomAtomizeFunctions.count(SectionName) && |
| "Custom atomizer for this section already exists"); |
| CustomAtomizeFunctions[SectionName] = std::move(Atomizer); |
| } |
| |
| bool MachOAtomGraphBuilder::areLayoutLocked(const Atom &A, const Atom &B) { |
| // If these atoms are the same then they're trivially "locked". |
| if (&A == &B) |
| return true; |
| |
| // If A and B are different, check whether either is undefined. (in which |
| // case they are not locked). |
| if (!A.isDefined() || !B.isDefined()) |
| return false; |
| |
| // A and B are different, but they're both defined atoms. We need to check |
| // whether they're part of the same alt_entry chain. |
| auto &DA = static_cast<const DefinedAtom &>(A); |
| auto &DB = static_cast<const DefinedAtom &>(B); |
| |
| auto AStartItr = AltEntryStarts.find(&DA); |
| if (AStartItr == AltEntryStarts.end()) // If A is not in a chain bail out. |
| return false; |
| |
| auto BStartItr = AltEntryStarts.find(&DB); |
| if (BStartItr == AltEntryStarts.end()) // If B is not in a chain bail out. |
| return false; |
| |
| // A and B are layout locked if they're in the same chain. |
| return AStartItr->second == BStartItr->second; |
| } |
| |
| unsigned |
| MachOAtomGraphBuilder::getPointerSize(const object::MachOObjectFile &Obj) { |
| return Obj.is64Bit() ? 8 : 4; |
| } |
| |
| support::endianness |
| MachOAtomGraphBuilder::getEndianness(const object::MachOObjectFile &Obj) { |
| return Obj.isLittleEndian() ? support::little : support::big; |
| } |
| |
| MachOAtomGraphBuilder::MachOSection &MachOAtomGraphBuilder::getCommonSection() { |
| if (!CommonSymbolsSection) { |
| auto Prot = static_cast<sys::Memory::ProtectionFlags>( |
| sys::Memory::MF_READ | sys::Memory::MF_WRITE); |
| auto &GenericSection = G->createSection("<common>", 1, Prot, true); |
| CommonSymbolsSection = MachOSection(GenericSection); |
| } |
| return *CommonSymbolsSection; |
| } |
| |
| Error MachOAtomGraphBuilder::parseSections() { |
| for (auto &SecRef : Obj.sections()) { |
| assert((SecRef.getAlignment() <= std::numeric_limits<uint32_t>::max()) && |
| "Section alignment does not fit in 32 bits"); |
| |
| StringRef Name; |
| if (auto EC = SecRef.getName(Name)) |
| return errorCodeToError(EC); |
| |
| unsigned SectionIndex = SecRef.getIndex() + 1; |
| |
| uint32_t Align = SecRef.getAlignment(); |
| if (!isPowerOf2_32(Align)) |
| return make_error<JITLinkError>("Section " + Name + |
| " has non-power-of-2 " |
| "alignment"); |
| |
| // FIXME: Get real section permissions |
| // How, exactly, on MachO? |
| sys::Memory::ProtectionFlags Prot; |
| if (SecRef.isText()) |
| Prot = static_cast<sys::Memory::ProtectionFlags>(sys::Memory::MF_READ | |
| sys::Memory::MF_EXEC); |
| else |
| Prot = static_cast<sys::Memory::ProtectionFlags>(sys::Memory::MF_READ | |
| sys::Memory::MF_WRITE); |
| |
| auto &GenericSection = G->createSection(Name, Align, Prot, SecRef.isBSS()); |
| |
| LLVM_DEBUG({ |
| dbgs() << "Adding section " << Name << ": " |
| << format("0x%016" PRIx64, SecRef.getAddress()) |
| << ", align: " << SecRef.getAlignment() << "\n"; |
| }); |
| |
| assert(!Sections.count(SectionIndex) && "Section index already in use"); |
| |
| auto &MachOSec = |
| Sections |
| .try_emplace(SectionIndex, GenericSection, SecRef.getAddress(), |
| SecRef.getAlignment()) |
| .first->second; |
| |
| if (!SecRef.isVirtual()) { |
| // If this section has content then record it. |
| Expected<StringRef> Content = SecRef.getContents(); |
| if (!Content) |
| return Content.takeError(); |
| if (Content->size() != SecRef.getSize()) |
| return make_error<JITLinkError>("Section content size does not match " |
| "declared size for " + |
| Name); |
| MachOSec.setContent(*Content); |
| } else { |
| // If this is a zero-fill section then just record the size. |
| MachOSec.setZeroFill(SecRef.getSize()); |
| } |
| |
| uint32_t SectionFlags = |
| Obj.is64Bit() ? Obj.getSection64(SecRef.getRawDataRefImpl()).flags |
| : Obj.getSection(SecRef.getRawDataRefImpl()).flags; |
| |
| MachOSec.setNoDeadStrip(SectionFlags & MachO::S_ATTR_NO_DEAD_STRIP); |
| } |
| |
| return Error::success(); |
| } |
| |
| // Adds atoms with identified start addresses (but not lengths) for all named |
| // atoms. |
| // Also, for every section that contains named atoms, but does not have an |
| // atom at offset zero of that section, constructs an anonymous atom covering |
| // that range. |
| Error MachOAtomGraphBuilder::addNonCustomAtoms() { |
| using AddrToAtomMap = std::map<JITTargetAddress, DefinedAtom *>; |
| DenseMap<MachOSection *, AddrToAtomMap> SecToAtoms; |
| |
| DenseMap<MachOSection *, unsigned> FirstOrdinal; |
| std::vector<DefinedAtom *> AltEntryAtoms; |
| |
| DenseSet<StringRef> ProcessedSymbols; // Used to check for duplicate defs. |
| |
| for (auto SymI = Obj.symbol_begin(), SymE = Obj.symbol_end(); SymI != SymE; |
| ++SymI) { |
| object::SymbolRef Sym(SymI->getRawDataRefImpl(), &Obj); |
| |
| auto Name = Sym.getName(); |
| if (!Name) |
| return Name.takeError(); |
| |
| // Bail out on duplicate definitions: There should never be more than one |
| // definition for a symbol in a given object file. |
| if (ProcessedSymbols.count(*Name)) |
| return make_error<JITLinkError>("Duplicate definition within object: " + |
| *Name); |
| else |
| ProcessedSymbols.insert(*Name); |
| |
| auto Addr = Sym.getAddress(); |
| if (!Addr) |
| return Addr.takeError(); |
| |
| auto SymType = Sym.getType(); |
| if (!SymType) |
| return SymType.takeError(); |
| |
| auto Flags = Sym.getFlags(); |
| |
| if (Flags & object::SymbolRef::SF_Undefined) { |
| LLVM_DEBUG(dbgs() << "Adding undef atom \"" << *Name << "\"\n"); |
| G->addExternalAtom(*Name); |
| continue; |
| } else if (Flags & object::SymbolRef::SF_Absolute) { |
| LLVM_DEBUG(dbgs() << "Adding absolute \"" << *Name << "\" addr: " |
| << format("0x%016" PRIx64, *Addr) << "\n"); |
| auto &A = G->addAbsoluteAtom(*Name, *Addr); |
| A.setGlobal(Flags & object::SymbolRef::SF_Global); |
| A.setExported(Flags & object::SymbolRef::SF_Exported); |
| A.setWeak(Flags & object::SymbolRef::SF_Weak); |
| continue; |
| } else if (Flags & object::SymbolRef::SF_Common) { |
| LLVM_DEBUG({ |
| dbgs() << "Adding common \"" << *Name |
| << "\" addr: " << format("0x%016" PRIx64, *Addr) << "\n"; |
| }); |
| auto &A = |
| G->addCommonAtom(getCommonSection().getGenericSection(), *Name, *Addr, |
| std::max(Sym.getAlignment(), 1U), |
| Obj.getCommonSymbolSize(Sym.getRawDataRefImpl())); |
| A.setGlobal(Flags & object::SymbolRef::SF_Global); |
| A.setExported(Flags & object::SymbolRef::SF_Exported); |
| continue; |
| } |
| |
| LLVM_DEBUG(dbgs() << "Adding defined atom \"" << *Name << "\"\n"); |
| |
| // This atom is neither undefined nor absolute, so it must be defined in |
| // this object. Get its section index. |
| auto SecItr = Sym.getSection(); |
| if (!SecItr) |
| return SecItr.takeError(); |
| |
| uint64_t SectionIndex = (*SecItr)->getIndex() + 1; |
| |
| LLVM_DEBUG(dbgs() << " to section index " << SectionIndex << "\n"); |
| |
| auto SecByIndexItr = Sections.find(SectionIndex); |
| if (SecByIndexItr == Sections.end()) |
| return make_error<JITLinkError>("Unrecognized section index in macho"); |
| |
| auto &Sec = SecByIndexItr->second; |
| |
| auto &DA = G->addDefinedAtom(Sec.getGenericSection(), *Name, *Addr, |
| std::max(Sym.getAlignment(), 1U)); |
| |
| DA.setGlobal(Flags & object::SymbolRef::SF_Global); |
| DA.setExported(Flags & object::SymbolRef::SF_Exported); |
| DA.setWeak(Flags & object::SymbolRef::SF_Weak); |
| |
| DA.setCallable(*SymType & object::SymbolRef::ST_Function); |
| |
| // Check NDesc flags. |
| { |
| uint16_t NDesc = 0; |
| if (Obj.is64Bit()) |
| NDesc = Obj.getSymbol64TableEntry(SymI->getRawDataRefImpl()).n_desc; |
| else |
| NDesc = Obj.getSymbolTableEntry(SymI->getRawDataRefImpl()).n_desc; |
| |
| // Record atom for alt-entry post-processing (where the layout-next |
| // constraints will be added). |
| if (NDesc & MachO::N_ALT_ENTRY) |
| AltEntryAtoms.push_back(&DA); |
| |
| // If this atom has a no-dead-strip attr attached then mark it live. |
| if (NDesc & MachO::N_NO_DEAD_STRIP) |
| DA.setLive(true); |
| } |
| |
| LLVM_DEBUG({ |
| dbgs() << " Added " << *Name |
| << " addr: " << format("0x%016" PRIx64, *Addr) |
| << ", align: " << DA.getAlignment() |
| << ", section: " << Sec.getGenericSection().getName() << "\n"; |
| }); |
| |
| auto &SecAtoms = SecToAtoms[&Sec]; |
| SecAtoms[DA.getAddress() - Sec.getAddress()] = &DA; |
| } |
| |
| // Add anonymous atoms. |
| for (auto &KV : Sections) { |
| auto &S = KV.second; |
| |
| // Skip empty sections. |
| if (S.empty()) |
| continue; |
| |
| // Skip sections with custom handling. |
| if (CustomAtomizeFunctions.count(S.getName())) |
| continue; |
| |
| auto SAI = SecToAtoms.find(&S); |
| |
| // If S is not in the SecToAtoms map then it contained no named atom. Add |
| // one anonymous atom to cover the whole section. |
| if (SAI == SecToAtoms.end()) { |
| SecToAtoms[&S][0] = &G->addAnonymousAtom( |
| S.getGenericSection(), S.getAddress(), S.getAlignment()); |
| continue; |
| } |
| |
| // Otherwise, check whether this section had an atom covering offset zero. |
| // If not, add one. |
| auto &SecAtoms = SAI->second; |
| if (!SecAtoms.count(0)) |
| SecAtoms[0] = &G->addAnonymousAtom(S.getGenericSection(), S.getAddress(), |
| S.getAlignment()); |
| } |
| |
| LLVM_DEBUG(dbgs() << "MachOGraphBuilder setting atom content\n"); |
| |
| // Set atom contents and any section-based flags. |
| for (auto &KV : SecToAtoms) { |
| auto &S = *KV.first; |
| auto &SecAtoms = KV.second; |
| |
| // Iterate the atoms in reverse order and set up their contents. |
| JITTargetAddress LastAtomAddr = S.getSize(); |
| for (auto I = SecAtoms.rbegin(), E = SecAtoms.rend(); I != E; ++I) { |
| auto Offset = I->first; |
| auto &A = *I->second; |
| LLVM_DEBUG({ |
| dbgs() << " " << A << " to [ " << S.getAddress() + Offset << " .. " |
| << S.getAddress() + LastAtomAddr << " ]\n"; |
| }); |
| |
| if (S.isZeroFill()) |
| A.setZeroFill(LastAtomAddr - Offset); |
| else |
| A.setContent(S.getContent().substr(Offset, LastAtomAddr - Offset)); |
| |
| // If the section has no-dead-strip set then mark the atom as live. |
| if (S.isNoDeadStrip()) |
| A.setLive(true); |
| |
| LastAtomAddr = Offset; |
| } |
| } |
| |
| LLVM_DEBUG(dbgs() << "Adding alt-entry starts\n"); |
| |
| // Sort alt-entry atoms by address in ascending order. |
| llvm::sort(AltEntryAtoms.begin(), AltEntryAtoms.end(), |
| [](const DefinedAtom *LHS, const DefinedAtom *RHS) { |
| return LHS->getAddress() < RHS->getAddress(); |
| }); |
| |
| // Process alt-entry atoms in address order to build the table of alt-entry |
| // atoms to alt-entry chain starts. |
| for (auto *DA : AltEntryAtoms) { |
| assert(!AltEntryStarts.count(DA) && "Duplicate entry in AltEntryStarts"); |
| |
| // DA is an alt-entry atom. Look for the predecessor atom that it is locked |
| // to, bailing out if we do not find one. |
| auto AltEntryPred = G->findAtomByAddress(DA->getAddress() - 1); |
| if (!AltEntryPred) |
| return AltEntryPred.takeError(); |
| |
| // Add a LayoutNext edge from the predecessor to this atom. |
| AltEntryPred->setLayoutNext(*DA); |
| |
| // Check to see whether the predecessor itself is an alt-entry atom. |
| auto AltEntryStartItr = AltEntryStarts.find(&*AltEntryPred); |
| if (AltEntryStartItr != AltEntryStarts.end()) { |
| // If the predecessor was an alt-entry atom then re-use its value. |
| LLVM_DEBUG({ |
| dbgs() << " " << *DA << " -> " << *AltEntryStartItr->second |
| << " (based on existing entry for " << *AltEntryPred << ")\n"; |
| }); |
| AltEntryStarts[DA] = AltEntryStartItr->second; |
| } else { |
| // If the predecessor does not have an entry then add an entry for this |
| // atom (i.e. the alt_entry atom) and a self-reference entry for the |
| /// predecessory atom that is the start of this chain. |
| LLVM_DEBUG({ |
| dbgs() << " " << *AltEntryPred << " -> " << *AltEntryPred << "\n" |
| << " " << *DA << " -> " << *AltEntryPred << "\n"; |
| }); |
| AltEntryStarts[&*AltEntryPred] = &*AltEntryPred; |
| AltEntryStarts[DA] = &*AltEntryPred; |
| } |
| } |
| |
| return Error::success(); |
| } |
| |
| Error MachOAtomGraphBuilder::addAtoms() { |
| // Add all named atoms. |
| if (auto Err = addNonCustomAtoms()) |
| return Err; |
| |
| // Process special sections. |
| for (auto &KV : Sections) { |
| auto &S = KV.second; |
| auto HI = CustomAtomizeFunctions.find(S.getGenericSection().getName()); |
| if (HI != CustomAtomizeFunctions.end()) { |
| auto &Atomize = HI->second; |
| if (auto Err = Atomize(S)) |
| return Err; |
| } |
| } |
| |
| return Error::success(); |
| } |
| |
| } // end namespace jitlink |
| } // end namespace llvm |