//=--------- 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> 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(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(A); auto &DB = static_cast(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::MF_READ | sys::Memory::MF_WRITE); auto &GenericSection = G->createSection("", 1, Prot, true); CommonSymbolsSection = MachOSection(GenericSection); } return *CommonSymbolsSection; } Error MachOAtomGraphBuilder::parseSections() { for (auto &SecRef : Obj.sections()) { assert((SecRef.getAlignment() <= std::numeric_limits::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("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::MF_READ | sys::Memory::MF_EXEC); else Prot = static_cast(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 Content = SecRef.getContents(); if (!Content) return Content.takeError(); if (Content->size() != SecRef.getSize()) return make_error("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; DenseMap SecToAtoms; DenseMap FirstOrdinal; std::vector AltEntryAtoms; DenseSet 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("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("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