diff --git a/lld/MachO/Arch/ARM64.cpp b/lld/MachO/Arch/ARM64.cpp --- a/lld/MachO/Arch/ARM64.cpp +++ b/lld/MachO/Arch/ARM64.cpp @@ -13,6 +13,7 @@ #include "Target.h" #include "lld/Common/ErrorHandler.h" +#include "mach-o/compact_unwind_encoding.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/BinaryFormat/MachO.h" @@ -141,6 +142,10 @@ backwardBranchRange = 128 * 1024 * 1024; forwardBranchRange = backwardBranchRange - 4; + modeDwarfEncoding = UNWIND_ARM64_MODE_DWARF; + subtractorRelocType = ARM64_RELOC_SUBTRACTOR; + unsignedRelocType = ARM64_RELOC_UNSIGNED; + stubHelperHeaderSize = sizeof(stubHelperHeaderCode); stubHelperEntrySize = sizeof(stubHelperEntryCode); } diff --git a/lld/MachO/Arch/ARM64_32.cpp b/lld/MachO/Arch/ARM64_32.cpp --- a/lld/MachO/Arch/ARM64_32.cpp +++ b/lld/MachO/Arch/ARM64_32.cpp @@ -105,6 +105,10 @@ cpuType = CPU_TYPE_ARM64_32; cpuSubtype = CPU_SUBTYPE_ARM64_V8; + modeDwarfEncoding = 0x04000000; // UNWIND_ARM_MODE_DWARF + subtractorRelocType = GENERIC_RELOC_INVALID; // FIXME + unsignedRelocType = GENERIC_RELOC_INVALID; // FIXME + stubSize = sizeof(stubCode); stubHelperHeaderSize = sizeof(stubHelperHeaderCode); stubHelperEntrySize = sizeof(stubHelperEntryCode); diff --git a/lld/MachO/Arch/X86_64.cpp b/lld/MachO/Arch/X86_64.cpp --- a/lld/MachO/Arch/X86_64.cpp +++ b/lld/MachO/Arch/X86_64.cpp @@ -12,6 +12,7 @@ #include "Target.h" #include "lld/Common/ErrorHandler.h" +#include "mach-o/compact_unwind_encoding.h" #include "llvm/BinaryFormat/MachO.h" #include "llvm/Support/Endian.h" @@ -185,6 +186,10 @@ cpuType = CPU_TYPE_X86_64; cpuSubtype = CPU_SUBTYPE_X86_64_ALL; + modeDwarfEncoding = UNWIND_X86_MODE_DWARF; + subtractorRelocType = X86_64_RELOC_SUBTRACTOR; + unsignedRelocType = X86_64_RELOC_UNSIGNED; + stubSize = sizeof(stub); stubHelperHeaderSize = sizeof(stubHelperHeader); stubHelperEntrySize = sizeof(stubHelperEntry); diff --git a/lld/MachO/CMakeLists.txt b/lld/MachO/CMakeLists.txt --- a/lld/MachO/CMakeLists.txt +++ b/lld/MachO/CMakeLists.txt @@ -14,6 +14,7 @@ Driver.cpp DriverUtils.cpp Dwarf.cpp + EhFrame.cpp ExportTrie.cpp ICF.cpp InputFiles.cpp diff --git a/lld/MachO/Config.h b/lld/MachO/Config.h --- a/lld/MachO/Config.h +++ b/lld/MachO/Config.h @@ -130,6 +130,9 @@ bool dedupLiterals = true; bool omitDebugInfo = false; bool warnDylibInstallName = false; + // Temporary config flag that will be removed once we have fully implemented + // support for __eh_frame. + bool parseEhFrames = false; uint32_t headerPad; uint32_t dylibCompatibilityVersion = 0; uint32_t dylibCurrentVersion = 0; diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -1039,8 +1039,9 @@ int inputOrder = 0; for (const InputFile *file : inputFiles) { for (const Section *section : file->sections) { + // Compact unwind entries require special handling elsewhere. (In + // contrast, EH frames are handled like regular ConcatInputSections.) if (section->name == section_names::compactUnwind) - // Compact unwind entries require special handling elsewhere. continue; ConcatOutputSection *osec = nullptr; for (const Subsection &subsection : section->subsections) { @@ -1302,6 +1303,7 @@ config->callGraphProfileSort = args.hasFlag( OPT_call_graph_profile_sort, OPT_no_call_graph_profile_sort, true); config->printSymbolOrder = args.getLastArgValue(OPT_print_symbol_order); + config->parseEhFrames = static_cast(getenv("LLD_IN_TEST")); // FIXME: Add a commandline flag for this too. config->zeroModTime = getenv("ZERO_AR_DATE"); diff --git a/lld/MachO/EhFrame.h b/lld/MachO/EhFrame.h new file mode 100644 --- /dev/null +++ b/lld/MachO/EhFrame.h @@ -0,0 +1,120 @@ +//===- EhFrame.h ------------------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLD_MACHO_EH_FRAME_H +#define LLD_MACHO_EH_FRAME_H + +#include "InputSection.h" +#include "Relocations.h" + +#include "lld/Common/LLVM.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/SmallVector.h" + +/* + * NOTE: The main bulk of the EH frame parsing logic is in InputFiles.cpp as it + * is closely coupled with other file parsing logic; EhFrame.h just contains a + * few helpers. + */ + +/* + * === The EH frame format === + * + * EH frames can either be Common Information Entries (CIEs) or Frame + * Description Entries (FDEs). CIEs contain information that is common amongst + * several FDEs. Each FDE contains a pointer to its CIE. Thus all the EH frame + * entries together form a forest of two-level trees, with CIEs as the roots + * and FDEs as the leaves. Note that a CIE must precede the FDEs which point + * to it. + * + * A CIE comprises the following fields in order: + * 1. Length of the entry (4 or 12 bytes) + * 2. CIE offset (4 bytes; always 0 for CIEs) + * 3. CIE version (byte) + * 4. Null-terminated augmentation string + * 5-8. LEB128 values that we don't care about + * 9. Augmentation data, to be interpreted using the aug string + * 10. DWARF instructions (ignored by LLD) + * + * An FDE comprises of the following: + * 1. Length of the entry (4 or 12 bytes) + * 2. CIE offset (4 bytes pcrel offset that points backwards to this FDE's CIE) + * 3. Function address (pointer-sized pcrel offset) + * 4. (Optional) Augmentation data length + * 5. (Optional) LSDA address (pointer-sized pcrel offset) + * 6. DWARF instructions (ignored by LLD) + */ +namespace lld { +namespace macho { + +class EhReader { +public: + EhReader(const ObjFile *file, ArrayRef data, size_t dataOff, + size_t wordSize) + : file(file), data(data), dataOff(dataOff), wordSize(wordSize) {} + size_t size() const { return data.size(); } + // Read and validate the length field. + uint64_t readLength(size_t *off) const; + // Skip the length field without doing validation. + void skipValidLength(size_t *off) const; + uint8_t readByte(size_t *off) const; + uint32_t readU32(size_t *off) const; + uint64_t readPointer(size_t *off) const; + StringRef readString(size_t *off) const; + void skipLeb128(size_t *off) const; + void failOn(size_t errOff, const Twine &msg) const; + +private: + const ObjFile *file; + ArrayRef data; + // The offset of the data array within its section. Used only for error + // reporting. + const size_t dataOff; + size_t wordSize; +}; + +// The EH frame format, when emitted by llvm-mc, consists of a number of +// "abs-ified" relocations, i.e. relocations that are implicitly encoded as +// pcrel offsets in the section data. The offsets refer to the locations of +// symbols in the input object file. When we ingest these EH frames, we convert +// these implicit relocations into explicit Relocs. +// +// These pcrel relocations are semantically similar to X86_64_RELOC_SIGNED_4. +// However, we need this operation to be cross-platform, and ARM does not have a +// similar relocation that is applicable. We therefore use the more verbose (but +// more generic) subtractor relocation to encode these pcrel values. ld64 +// appears to do something similar -- its `-r` output contains these explicit +// subtractor relocations. +class EhRelocator { +public: + EhRelocator(InputSection *isec) : isec(isec) {} + + // For the next two methods, let `PC` denote `isec address + off`. + // Create relocs writing the value of target - PC to PC. + void makePcRel(uint64_t off, + llvm::PointerUnion target, + uint8_t length); + // Create relocs writing the value of PC - target to PC. + void makeNegativePcRel(uint64_t off, + llvm::PointerUnion target, + uint8_t length); + // Insert the new relocations into isec->relocs. + void commit(); + +private: + InputSection *isec; + // Insert new relocs here so that we don't invalidate iterators into the + // existing relocs vector. + SmallVector newRelocs; +}; + +} // namespace macho +} // namespace lld + +#endif diff --git a/lld/MachO/EhFrame.cpp b/lld/MachO/EhFrame.cpp new file mode 100644 --- /dev/null +++ b/lld/MachO/EhFrame.cpp @@ -0,0 +1,140 @@ +//===- EhFrame.cpp --------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "EhFrame.h" +#include "InputFiles.h" + +#include "lld/Common/ErrorHandler.h" +#include "llvm/BinaryFormat/Dwarf.h" +#include "llvm/Support/Endian.h" + +using namespace llvm; +using namespace lld; +using namespace lld::macho; +using namespace llvm::support::endian; + +uint64_t EhReader::readLength(size_t *off) const { + const size_t errOff = *off; + if (*off + 4 > data.size()) + failOn(errOff, "CIE/FDE too small"); + uint64_t len = read32le(data.data() + *off); + *off += 4; + if (len == dwarf::DW_LENGTH_DWARF64) { + // FIXME: test this DWARF64 code path + if (*off + 8 > data.size()) + failOn(errOff, "CIE/FDE too small"); + len = read64le(data.data() + *off); + *off += 8; + } + if (*off + len > data.size()) + failOn(errOff, "CIE/FDE extends past the end of the section"); + return len; +} + +void EhReader::skipValidLength(size_t *off) const { + uint32_t len = read32le(data.data() + *off); + *off += 4; + if (len == dwarf::DW_LENGTH_DWARF64) + *off += 8; +} + +// Read a byte and advance off by one byte. +uint8_t EhReader::readByte(size_t *off) const { + if (*off + 1 > data.size()) + failOn(*off, "unexpected end of CIE/FDE"); + return data[(*off)++]; +} + +uint32_t EhReader::readU32(size_t *off) const { + if (*off + 4 > data.size()) + failOn(*off, "unexpected end of CIE/FDE"); + uint32_t v = read32le(data.data() + *off); + *off += 4; + return v; +} + +uint64_t EhReader::readPointer(size_t *off) const { + if (*off + wordSize > data.size()) + failOn(*off, "unexpected end of CIE/FDE"); + uint64_t v; + if (wordSize == 8) + v = read64le(data.data() + *off); + else { + assert(wordSize == 4); + v = read32le(data.data() + *off); + } + *off += wordSize; + return v; +} + +// Read a null-terminated string. +StringRef EhReader::readString(size_t *off) const { + if (*off > data.size()) + failOn(*off, "corrupted CIE (failed to read string)"); + const size_t maxlen = data.size() - *off; + auto *c = reinterpret_cast(data.data() + *off); + size_t len = strnlen(c, maxlen); + if (len == maxlen) // we failed to find the null terminator + failOn(*off, "corrupted CIE (failed to read string)"); + *off += len + 1; // skip the null byte too + return StringRef(c, len); +} + +void EhReader::skipLeb128(size_t *off) const { + const size_t errOff = *off; + while (*off < data.size()) { + uint8_t val = data[(*off)++]; + if ((val & 0x80) == 0) + return; + } + failOn(errOff, "corrupted CIE (failed to read LEB128)"); +} + +void EhReader::failOn(size_t errOff, const Twine &msg) const { + fatal(toString(file) + ":(__eh_frame+0x" + + Twine::utohexstr(dataOff + errOff) + "): " + msg); +} + +/* + * Create a pair of relocs to write the value of: + * `b - (offset + a)` if Invert == false + * `(a + offset) - b` if Invert == true + */ +template +static void createSubtraction(PointerUnion a, + PointerUnion b, + uint64_t off, uint8_t length, + SmallVectorImpl *newRelocs) { + auto subtrahend = a; + auto minuend = b; + if (Invert) + std::swap(subtrahend, minuend); + assert(subtrahend.is()); + Reloc subtrahendReloc(target->subtractorRelocType, /*pcrel=*/false, length, + off, /*addend=*/0, subtrahend); + Reloc minuendReloc(target->unsignedRelocType, /*pcrel=*/false, length, off, + (Invert ? 1 : -1) * off, minuend); + newRelocs->push_back(subtrahendReloc); + newRelocs->push_back(minuendReloc); +} + +void EhRelocator::makePcRel(uint64_t off, + PointerUnion target, + uint8_t length) { + createSubtraction(isec->symbols[0], target, off, length, &newRelocs); +} + +void EhRelocator::makeNegativePcRel( + uint64_t off, PointerUnion target, + uint8_t length) { + createSubtraction(isec, target, off, length, &newRelocs); +} + +void EhRelocator::commit() { + isec->relocs.insert(isec->relocs.end(), newRelocs.begin(), newRelocs.end()); +} diff --git a/lld/MachO/ICF.cpp b/lld/MachO/ICF.cpp --- a/lld/MachO/ICF.cpp +++ b/lld/MachO/ICF.cpp @@ -212,9 +212,9 @@ // info matches. For simplicity, we only handle the case where there are only // symbols at offset zero within the section (which is typically the case with // .subsections_via_symbols.) - auto hasCU = [](Defined *d) { return d->unwindEntry != nullptr; }; - auto itA = std::find_if(ia->symbols.begin(), ia->symbols.end(), hasCU); - auto itB = std::find_if(ib->symbols.begin(), ib->symbols.end(), hasCU); + auto hasUnwind = [](Defined *d) { return d->unwindEntry != nullptr; }; + auto itA = std::find_if(ia->symbols.begin(), ia->symbols.end(), hasUnwind); + auto itB = std::find_if(ib->symbols.begin(), ib->symbols.end(), hasUnwind); if (itA == ia->symbols.end()) return itB == ib->symbols.end(); if (itB == ib->symbols.end()) diff --git a/lld/MachO/InputFiles.h b/lld/MachO/InputFiles.h --- a/lld/MachO/InputFiles.h +++ b/lld/MachO/InputFiles.h @@ -60,7 +60,8 @@ using Subsections = std::vector; class InputFile; -struct Section { +class Section { +public: InputFile *file; StringRef segname; StringRef name; @@ -76,6 +77,13 @@ Section &operator=(const Section &) = delete; Section(Section &&) = delete; Section &operator=(Section &&) = delete; + +private: + // Whether we have already split this section into individual subsections. + // For sections that cannot be split (e.g. literal sections), this is always + // false. + bool doneSplitting = false; + friend class ObjFile; }; // Represents a call graph profile edge. @@ -135,6 +143,12 @@ static int idCount; }; +struct FDE { + uint32_t funcLength; + Symbol *personality; + InputSection *lsda; +}; + // .o file class ObjFile final : public InputFile { public: @@ -146,10 +160,11 @@ static bool classof(const InputFile *f) { return f->kind() == ObjKind; } llvm::DWARFUnit *compileUnit = nullptr; + Section *addrSigSection = nullptr; const uint32_t modTime; std::vector debugSections; std::vector callGraph; - Section *addrSigSection = nullptr; + llvm::DenseMap fdes; private: template void parseLazy(); @@ -164,7 +179,9 @@ void parseRelocations(ArrayRef sectionHeaders, const SectionHeader &, Section &); void parseDebugInfo(); + void splitEhFrames(ArrayRef dataArr, Section &ehFrameSection); void registerCompactUnwind(Section &compactUnwindSection); + void registerEhFrames(Section &ehFrameSection); }; // command-line -sectcreate file diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp --- a/lld/MachO/InputFiles.cpp +++ b/lld/MachO/InputFiles.cpp @@ -45,6 +45,7 @@ #include "Config.h" #include "Driver.h" #include "Dwarf.h" +#include "EhFrame.h" #include "ExportTrie.h" #include "InputSection.h" #include "MachOStructs.h" @@ -323,6 +324,7 @@ section, data.slice(off, recordSize), align); subsections.push_back({off, isec}); } + section.doneSplitting = true; }; if (sectionType(sec.flags) == S_CSTRING_LITERALS || @@ -344,6 +346,9 @@ section.subsections.push_back({0, isec}); } else if (auto recordSize = getRecordSize(segname, name)) { splitRecords(*recordSize); + } else if (config->parseEhFrames && name == section_names::ehFrame && + segname == segment_names::text) { + splitEhFrames(data, *sections.back()); } else if (segname == segment_names::llvm) { if (config->callGraphProfileSort && name == section_names::cgProfile) checkError(parseCallGraph(data, callGraph)); @@ -371,6 +376,45 @@ } } +void ObjFile::splitEhFrames(ArrayRef data, Section &ehFrameSection) { + EhReader reader(this, data, /*dataOff=*/0, target->wordSize); + size_t off = 0; + while (off < reader.size()) { + uint64_t frameOff = off; + uint64_t length = reader.readLength(&off); + if (length == 0) + break; + uint64_t fullLength = length + (off - frameOff); + off += length; + // We hard-code an alignment of 1 here because we don't actually want our + // EH frames to be aligned to the section alignment. EH frame decoders don't + // expect this alignment. Moreover, each EH frame must start where the + // previous one ends, and where it ends is indicated by the length field. + // Unless we update the length field (troublesome), we should keep the + // alignment to 1. + // Note that we still want to preserve the alignment of the overall section, + // just not of the individual EH frames. + ehFrameSection.subsections.push_back( + {frameOff, make(ehFrameSection, + data.slice(frameOff, fullLength), + /*align=*/1)}); + } + ehFrameSection.doneSplitting = true; +} + +template +static Section *findContainingSection(const std::vector
§ions, + T *offset) { + static_assert(std::is_same::value || + std::is_same::value, + "unexpected type for offset"); + auto it = std::prev(llvm::upper_bound( + sections, *offset, + [](uint64_t value, const Section *sec) { return value < sec->addr; })); + *offset -= (*it)->addr; + return *it; +} + // Find the subsection corresponding to the greatest section offset that is <= // that of the given offset. // @@ -475,13 +519,6 @@ relocation_info relInfo = relInfos[i]; bool isSubtrahend = target->hasAttr(relInfo.r_type, RelocAttrBits::SUBTRAHEND); - if (isSubtrahend && StringRef(sec.sectname) == section_names::ehFrame) { - // __TEXT,__eh_frame only has symbols and SUBTRACTOR relocs when ld64 -r - // adds local "EH_Frame1" and "func.eh". Ignore them because they have - // gone unused by Mac OS since Snow Leopard (10.6), vintage 2009. - ++i; - continue; - } int64_t pairedAddend = 0; if (target->hasAttr(relInfo.r_type, RelocAttrBits::ADDEND)) { pairedAddend = SignExtend64<24>(relInfo.r_symbolnum); @@ -637,7 +674,8 @@ } assert(!isWeakDefCanBeHidden && "weak_def_can_be_hidden on already-hidden symbol?"); - bool includeInSymtab = !name.startswith("l") && !name.startswith("L"); + bool includeInSymtab = + !name.startswith("l") && !name.startswith("L") && !isEhFrameSection(isec); return make( name, isec->getFile(), isec, value, size, sym.n_desc & N_WEAK_DEF, /*isExternal=*/false, /*isPrivateExtern=*/false, includeInSymtab, @@ -730,20 +768,14 @@ Subsections &subsections = sections[i]->subsections; if (subsections.empty()) continue; - if (sections[i]->name == section_names::ehFrame) { - // __TEXT,__eh_frame only has symbols and SUBTRACTOR relocs when ld64 -r - // adds local "EH_Frame1" and "func.eh". Ignore them because they have - // gone unused by Mac OS since Snow Leopard (10.6), vintage 2009. - continue; - } std::vector &symbolIndices = symbolsBySection[i]; uint64_t sectionAddr = sectionHeaders[i].addr; uint32_t sectionAlign = 1u << sectionHeaders[i].align; - // Record-based sections have already been split into subsections during + // Some sections have already been split into subsections during // parseSections(), so we simply need to match Symbols to the corresponding // subsection here. - if (getRecordSize(sections[i]->segname, sections[i]->name)) { + if (sections[i]->doneSplitting) { for (size_t j = 0; j < symbolIndices.size(); ++j) { uint32_t symIndex = symbolIndices[j]; const NList &sym = nList[symIndex]; @@ -760,6 +792,7 @@ } continue; } + sections[i]->doneSplitting = true; // Calculate symbol sizes and create subsections by splitting the sections // along symbol boundaries. @@ -930,6 +963,8 @@ } if (compactUnwindSection) registerCompactUnwind(*compactUnwindSection); + if (config->parseEhFrames && ehFrameSection) + registerEhFrames(*ehFrameSection); } template void ObjFile::parseLazy() { @@ -1003,6 +1038,12 @@ // of the corresponding relocations.) We rely on `relocateCompactUnwind()` // to correctly handle these truncated input sections. isec->data = isec->data.slice(target->wordSize); + uint32_t encoding = read32le(isec->data.data() + sizeof(uint32_t)); + // llvm-mc omits CU entries for functions that need DWARF encoding, but + // `ld -r` doesn't. We can ignore them because we will re-synthesize these + // CU entries from the DWARF info during the output phase. + if ((encoding & target->modeDwarfEncoding) == target->modeDwarfEncoding) + continue; ConcatInputSection *referentIsec; for (auto it = isec->relocs.begin(); it != isec->relocs.end();) { @@ -1053,6 +1094,252 @@ } } +struct CIE { + macho::Symbol *personalitySymbol = nullptr; + bool fdesHaveLsda = false; + bool fdesHaveAug = false; +}; + +static CIE parseCIE(const InputSection *isec, const EhReader &reader, + size_t off) { + // Handling the full generality of possible DWARF encodings would be a major + // pain. We instead take advantage of our knowledge of how llvm-mc encodes + // DWARF and handle just that. + constexpr uint8_t expectedPersonalityEnc = + dwarf::DW_EH_PE_pcrel | dwarf::DW_EH_PE_indirect | dwarf::DW_EH_PE_sdata4; + constexpr uint8_t expectedPointerEnc = + dwarf::DW_EH_PE_pcrel | dwarf::DW_EH_PE_absptr; + + CIE cie; + uint8_t version = reader.readByte(&off); + if (version != 1 && version != 3) + fatal("Expected CIE version of 1 or 3, got " + Twine(version)); + StringRef aug = reader.readString(&off); + reader.skipLeb128(&off); // skip code alignment + reader.skipLeb128(&off); // skip data alignment + reader.skipLeb128(&off); // skip return address register + reader.skipLeb128(&off); // skip aug data length + uint64_t personalityAddrOff = 0; + for (char c : aug) { + switch (c) { + case 'z': + cie.fdesHaveAug = true; + break; + case 'P': { + uint8_t personalityEnc = reader.readByte(&off); + if (personalityEnc != expectedPersonalityEnc) + reader.failOn(off, "unexpected personality encoding 0x" + + Twine::utohexstr(personalityEnc)); + personalityAddrOff = off; + off += 4; + break; + } + case 'L': { + cie.fdesHaveLsda = true; + uint8_t lsdaEnc = reader.readByte(&off); + if (lsdaEnc != expectedPointerEnc) + reader.failOn(off, "unexpected LSDA encoding 0x" + + Twine::utohexstr(lsdaEnc)); + break; + } + case 'R': { + uint8_t pointerEnc = reader.readByte(&off); + if (pointerEnc != expectedPointerEnc) + reader.failOn(off, "unexpected pointer encoding 0x" + + Twine::utohexstr(pointerEnc)); + break; + } + default: + break; + } + } + if (personalityAddrOff != 0) { + auto personalityRelocIt = + llvm::find_if(isec->relocs, [=](const macho::Reloc &r) { + return r.offset == personalityAddrOff; + }); + if (personalityRelocIt == isec->relocs.end()) + reader.failOn(off, "Failed to locate relocation for personality symbol"); + cie.personalitySymbol = personalityRelocIt->referent.get(); + } + return cie; +} + +// EH frame target addresses may be encoded as pcrel offsets. However, instead +// of using an actual pcrel reloc, ld64 emits subtractor relocations instead. +// This function recovers the target address from the subtractors, essentially +// performing the inverse operation of EhRelocator. +// +// Concretely, we expect our relocations to write the value of `PC - +// target_addr` to `PC`. `PC` itself is denoted by a minuend relocation that +// points to a symbol or section plus an addend. +// +// If `Invert` is set, then we instead expect `target_addr - PC` to be written +// to `PC`. +template +Defined * +getTargetSymbolFromSubtraction(const InputSection *isec, + std::vector::iterator relocIt) { + const macho::Reloc &subtrahend = *relocIt; + const macho::Reloc &minuend = *std::next(relocIt); + assert(target->hasAttr(subtrahend.type, RelocAttrBits::SUBTRAHEND)); + assert(target->hasAttr(minuend.type, RelocAttrBits::UNSIGNED)); + // Note: pcSym may *not* be exactly at the PC; there's usually a non-zero + // addend. + auto *pcSym = cast(subtrahend.referent.get()); + Defined *target = + cast_or_null(minuend.referent.dyn_cast()); + if (!pcSym) { + auto *targetIsec = + cast(minuend.referent.get()); + target = findSymbolAtOffset(targetIsec, minuend.addend); + } + if (Invert) + std::swap(pcSym, target); + if (pcSym->isec != isec || + pcSym->value - (Invert ? -1 : 1) * minuend.addend != subtrahend.offset) + fatal("invalid FDE relocation in __eh_frame"); + return target; +} + +Defined *findSymbolAtAddress(const std::vector
§ions, + uint64_t addr) { + Section *sec = findContainingSection(sections, &addr); + auto *isec = cast(findContainingSubsection(*sec, &addr)); + return findSymbolAtOffset(isec, addr); +} + +// For symbols that don't have compact unwind info, associate them with the more +// general-purpose (and verbose) DWARF unwind info found in __eh_frame. +// +// This requires us to parse the contents of __eh_frame. See EhFrame.h for a +// description of its format. +// +// While parsing, we also look for what MC calls "abs-ified" relocations -- they +// are relocations which are implicitly encoded as offsets in the section data. +// We convert them into explicit Reloc structs so that the EH frames can be +// handled just like a regular ConcatInputSection later in our output phase. +// +// We also need to handle the case where our input object file has explicit +// relocations. This is the case when e.g. it's the output of `ld -r`. We only +// look for the "abs-ified" relocation if an explicit relocation is absent. +void ObjFile::registerEhFrames(Section &ehFrameSection) { + DenseMap cieMap; + for (const Subsection &subsec : ehFrameSection.subsections) { + auto *isec = cast(subsec.isec); + uint64_t isecOff = subsec.offset; + + // Subtractor relocs require the subtrahend to be a symbol reloc. Ensure + // that all EH frames have an associated symbol so that we can generate + // subtractor relocs that reference them. + if (isec->symbols.size() == 0) + isec->symbols.push_back(make( + "EH_Frame", isec->getFile(), isec, /*value=*/0, /*size=*/0, + /*isWeakDef=*/false, /*isExternal=*/false, /*isPrivateExtern=*/false, + /*includeInSymtab=*/false, /*isThumb=*/false, + /*isReferencedDynamically=*/false, /*noDeadStrip=*/false)); + else if (isec->symbols[0]->value != 0) + fatal("found symbol at unexpected offset in __eh_frame"); + + EhReader reader(this, isec->data, subsec.offset, target->wordSize); + size_t dataOff = 0; // Offset from the start of the EH frame. + reader.skipValidLength(&dataOff); // readLength() already validated this. + // cieOffOff is the offset from the start of the EH frame to the cieOff + // value, which is itself an offset from the current PC to a CIE. + const size_t cieOffOff = dataOff; + + EhRelocator ehRelocator(isec); + auto cieOffRelocIt = llvm::find_if( + isec->relocs, [=](const Reloc &r) { return r.offset == cieOffOff; }); + InputSection *cieIsec = nullptr; + if (cieOffRelocIt != isec->relocs.end()) { + // We already have an explicit relocation for the CIE offset. + cieIsec = + getTargetSymbolFromSubtraction(isec, cieOffRelocIt) + ->isec; + dataOff += sizeof(uint32_t); + } else { + // If we haven't found a relocation, then the CIE offset is most likely + // embedded in the section data (AKA an "abs-ified" reloc.). Parse that + // and generate a Reloc struct. + uint32_t cieMinuend = reader.readU32(&dataOff); + if (cieMinuend == 0) + cieIsec = isec; + else { + uint32_t cieOff = isecOff + dataOff - cieMinuend; + cieIsec = findContainingSubsection(ehFrameSection, &cieOff); + if (cieIsec == nullptr) + fatal("failed to find CIE"); + } + if (cieIsec != isec) + ehRelocator.makeNegativePcRel(cieOffOff, cieIsec->symbols[0], + /*length=*/2); + } + if (cieIsec == isec) { + cieMap[cieIsec] = parseCIE(isec, reader, dataOff); + continue; + } + + // Offset of the function address within the EH frame. + const size_t funcAddrOff = dataOff; + uint64_t funcAddr = reader.readPointer(&dataOff) + ehFrameSection.addr + + isecOff + funcAddrOff; + uint32_t funcLength = reader.readPointer(&dataOff); + size_t lsdaAddrOff = 0; // Offset of the LSDA address within the EH frame. + assert(cieMap.count(cieIsec)); + const CIE &cie = cieMap[cieIsec]; + Optional lsdaAddrOpt; + if (cie.fdesHaveAug) { + reader.skipLeb128(&dataOff); + lsdaAddrOff = dataOff; + if (cie.fdesHaveLsda) { + uint64_t lsdaOff = reader.readPointer(&dataOff); + if (lsdaOff != 0) // FIXME possible to test this? + lsdaAddrOpt = ehFrameSection.addr + isecOff + lsdaAddrOff + lsdaOff; + } + } + + auto funcAddrRelocIt = isec->relocs.end(); + auto lsdaAddrRelocIt = isec->relocs.end(); + for (auto it = isec->relocs.begin(); it != isec->relocs.end(); ++it) { + if (it->offset == funcAddrOff) + funcAddrRelocIt = it++; // Found subtrahend; skip over minuend reloc + else if (lsdaAddrOpt && it->offset == lsdaAddrOff) + lsdaAddrRelocIt = it++; // Found subtrahend; skip over minuend reloc + } + + Defined *funcSym; + if (funcAddrRelocIt != isec->relocs.end()) { + funcSym = getTargetSymbolFromSubtraction(isec, funcAddrRelocIt); + } else { + funcSym = findSymbolAtAddress(sections, funcAddr); + ehRelocator.makePcRel(funcAddrOff, funcSym, target->p2WordSize); + } + // The symbol has been coalesced, or already has a compact unwind entry. + if (!funcSym || funcSym->getFile() != this || funcSym->unwindEntry) { + // We must prune unused FDEs for correctness, so we cannot rely on + // -dead_strip being enabled. + isec->live = false; + continue; + } + + InputSection *lsdaIsec = nullptr; + if (lsdaAddrRelocIt != isec->relocs.end()) { + lsdaIsec = getTargetSymbolFromSubtraction(isec, lsdaAddrRelocIt)->isec; + } else if (lsdaAddrOpt) { + uint64_t lsdaAddr = *lsdaAddrOpt; + Section *sec = findContainingSection(sections, &lsdaAddr); + lsdaIsec = + cast(findContainingSubsection(*sec, &lsdaAddr)); + ehRelocator.makePcRel(lsdaAddrOff, lsdaIsec, target->p2WordSize); + } + + fdes[isec] = {funcLength, cie.personalitySymbol, lsdaIsec}; + funcSym->unwindEntry = isec; + ehRelocator.commit(); + } +} + // The path can point to either a dylib or a .tbd file. static DylibFile *loadDylib(StringRef path, DylibFile *umbrella) { Optional mbref = readFile(path); diff --git a/lld/MachO/InputSection.h b/lld/MachO/InputSection.h --- a/lld/MachO/InputSection.h +++ b/lld/MachO/InputSection.h @@ -273,6 +273,7 @@ bool isCodeSection(const InputSection *); bool isCfStringSection(const InputSection *); bool isClassRefsSection(const InputSection *); +bool isEhFrameSection(const InputSection *); extern std::vector inputSections; diff --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp --- a/lld/MachO/InputSection.cpp +++ b/lld/MachO/InputSection.cpp @@ -268,6 +268,11 @@ isec->getSegName() == segment_names::data; } +bool macho::isEhFrameSection(const InputSection *isec) { + return isec->getName() == section_names::ehFrame && + isec->getSegName() == segment_names::text; +} + std::string lld::toString(const InputSection *isec) { return (toString(isec->getFile()) + ":(" + isec->getName() + ")").str(); } diff --git a/lld/MachO/Relocations.h b/lld/MachO/Relocations.h --- a/lld/MachO/Relocations.h +++ b/lld/MachO/Relocations.h @@ -61,6 +61,13 @@ // gives the destination that this relocation refers to. int64_t addend = 0; llvm::PointerUnion referent = nullptr; + + Reloc() = default; + + Reloc(uint8_t type, bool pcrel, uint8_t length, uint32_t offset, + int64_t addend, llvm::PointerUnion referent) + : type(type), pcrel(pcrel), length(length), offset(offset), + addend(addend), referent(referent) {} }; bool validateSymbolRelocation(const Symbol *, const InputSection *, diff --git a/lld/MachO/Symbols.h b/lld/MachO/Symbols.h --- a/lld/MachO/Symbols.h +++ b/lld/MachO/Symbols.h @@ -183,6 +183,7 @@ uint64_t value; // size is only calculated for regular (non-bitcode) symbols. uint64_t size; + // This can be a subsection of either __compact_unwind or __eh_frame. ConcatInputSection *unwindEntry = nullptr; }; diff --git a/lld/MachO/Target.h b/lld/MachO/Target.h --- a/lld/MachO/Target.h +++ b/lld/MachO/Target.h @@ -14,6 +14,7 @@ #include "llvm/ADT/BitmaskEnum.h" #include "llvm/BinaryFormat/MachO.h" +#include "llvm/Support/MathExtras.h" #include "llvm/Support/MemoryBuffer.h" #include @@ -37,6 +38,7 @@ pageZeroSize = LP::pageZeroSize; headerSize = sizeof(typename LP::mach_header); wordSize = LP::wordSize; + p2WordSize = llvm::CTLog2(); } virtual ~TargetInfo() = default; @@ -85,12 +87,17 @@ size_t stubSize; size_t stubHelperHeaderSize; size_t stubHelperEntrySize; + uint8_t p2WordSize; size_t wordSize; size_t thunkSize = 0; uint64_t forwardBranchRange = 0; uint64_t backwardBranchRange = 0; + uint32_t modeDwarfEncoding; + uint8_t subtractorRelocType; + uint8_t unsignedRelocType; + // We contrive this value as sufficiently far from any valid address that it // will always be out-of-range for any architecture. UINT64_MAX is not a // good choice because it is (a) only 1 away from wrapping to 0, and (b) the diff --git a/lld/MachO/UnwindInfoSection.cpp b/lld/MachO/UnwindInfoSection.cpp --- a/lld/MachO/UnwindInfoSection.cpp +++ b/lld/MachO/UnwindInfoSection.cpp @@ -28,6 +28,7 @@ using namespace llvm; using namespace llvm::MachO; +using namespace llvm::support::endian; using namespace lld; using namespace lld::macho; @@ -222,7 +223,8 @@ // entries to the GOT. Hence the use of a MapVector for // UnwindInfoSection::symbols. for (const Defined *d : make_second_range(symbols)) - if (d->unwindEntry) + if (d->unwindEntry && + d->unwindEntry->getName() == section_names::compactUnwind) prepareRelocations(d->unwindEntry); } @@ -331,6 +333,18 @@ if (!d->unwindEntry) return; + // If we have DWARF unwind info, create a CU entry that points to it. + if (d->unwindEntry->getName() == section_names::ehFrame) { + cu.encoding = target->modeDwarfEncoding | d->unwindEntry->outSecOff; + const FDE &fde = cast(d->getFile())->fdes[d->unwindEntry]; + cu.functionLength = fde.funcLength; + cu.personality = fde.personality; + cu.lsda = fde.lsda; + return; + } + + assert(d->unwindEntry->getName() == section_names::compactUnwind); + auto buf = reinterpret_cast(d->unwindEntry->data.data()) - target->wordSize; cu.functionLength = diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp --- a/lld/MachO/Writer.cpp +++ b/lld/MachO/Writer.cpp @@ -950,8 +950,14 @@ StringRef segname = it.first.first; ConcatOutputSection *osec = it.second; assert(segname != segment_names::ld); - if (osec->isNeeded()) + if (osec->isNeeded()) { + // See comment in ObjFile::splitEhFrames() + if (osec->name == section_names::ehFrame && + segname == segment_names::text) + osec->align = target->wordSize; + getOrCreateOutputSegment(segname)->addOutputSection(osec); + } } for (SyntheticSection *ssec : syntheticSections) { diff --git a/lld/test/MachO/Inputs/eh-frame-x86_64-r.o b/lld/test/MachO/Inputs/eh-frame-x86_64-r.o new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@&1 | FileCheck %s --check-prefix TOO-SMALL-1 +# TOO-SMALL-1: error: {{.*}}too-small-1.o:(__eh_frame+0x0): CIE/FDE too small + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos10.15 %t/too-small-2.s -o %t/too-small-2.o +# RUN: not %lld -lSystem -dylib %t/too-small-2.o -o /dev/null 2>&1 | FileCheck %s --check-prefix TOO-SMALL-2 +# TOO-SMALL-2: error: {{.*}}too-small-2.o:(__eh_frame+0x0): CIE/FDE extends past the end of the section + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos10.15 %t/personality-enc.s -o %t/personality-enc.o +# RUN: not %lld -lSystem -dylib %t/personality-enc.o -o /dev/null 2>&1 | FileCheck %s --check-prefix PERS-ENC +# PERS-ENC: error: {{.*}}personality-enc.o:(__eh_frame+0x12): unexpected personality encoding 0xb + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos10.15 %t/pointer-enc.s -o %t/pointer-enc.o +# RUN: not %lld -lSystem -dylib %t/pointer-enc.o -o /dev/null 2>&1 | FileCheck %s --check-prefix PTR-ENC +# PTR-ENC: error: {{.*}}pointer-enc.o:(__eh_frame+0x11): unexpected pointer encoding 0x12 + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos10.15 %t/string-err.s -o %t/string-err.o +# RUN: not %lld -lSystem -dylib %t/string-err.o -o /dev/null 2>&1 | FileCheck %s --check-prefix STR +# STR: error: {{.*}}string-err.o:(__eh_frame+0x9): corrupted CIE (failed to read string) + +#--- too-small-1.s +.p2align 3 +.section __TEXT,__eh_frame +.short 0x3 + +.subsections_via_symbols + +#--- too-small-2.s +.p2align 3 +.section __TEXT,__eh_frame +.long 0x3 # length + +.subsections_via_symbols + +#--- personality-enc.s +.p2align 3 +.section __TEXT,__eh_frame + +.long 0x14 # length +.long 0 # CIE offset +.byte 1 # version +.asciz "zPR" # aug string +.byte 0x01 # code alignment +.byte 0x78 # data alignment +.byte 0x10 # return address register +.byte 0x01 # aug length +.byte 0x0b # personality encoding +.long 0xffff # personality pointer +.byte 0x10 # pointer encoding +.space 1 # pad to alignment + +.subsections_via_symbols + +#--- pointer-enc.s +.p2align 3 +.section __TEXT,__eh_frame + +.long 0x14 # length +.long 0 # CIE offset +.byte 1 # version +.asciz "zR" # aug string +.byte 0x01 # code alignment +.byte 0x78 # data alignment +.byte 0x10 # return address register +.byte 0x01 # aug length +.byte 0x12 # pointer encoding +.space 7 # pad to alignment + +.subsections_via_symbols + +#--- string-err.s +.p2align 3 +.section __TEXT,__eh_frame + +.long 0x7 # length +.long 0 # CIE offset +.byte 1 # version +.ascii "zR" # invalid aug string + +.subsections_via_symbols diff --git a/lld/test/MachO/obj-file-with-stabs.s b/lld/test/MachO/obj-file-with-stabs.s --- a/lld/test/MachO/obj-file-with-stabs.s +++ b/lld/test/MachO/obj-file-with-stabs.s @@ -1,27 +1,33 @@ # REQUIRES: x86 -## FIXME: This yaml is from an object file produced with 'ld -r' -## Replace this with "normal" .s test format once lld supports `-r` +## FIXME: This yaml is from an object file produced with 'ld -r': +## +## echo "int main() {return 1;}" > test.c +## clang -c -g -o test.o test.c +## ld -r -o test2.o test.o -no_data_in_code_info +## +## Replace this with "normal" .s test format once lld supports `-r` # RUN: yaml2obj %s -o %t.o -# RUN: %lld -lSystem -platform_version macos 11.3 11.0 -arch x86_64 %t.o -o %t +# RUN: %lld -lSystem -arch x86_64 %t.o -o %t + --- !mach-o FileHeader: magic: 0xFEEDFACF - cputype: 0x01000007 - cpusubtype: 0x00000003 - filetype: 0x00000001 - ncmds: 2 - sizeofcmds: 384 - flags: 0x00002000 - reserved: 0x00000000 + cputype: 0x1000007 + cpusubtype: 0x3 + filetype: 0x1 + ncmds: 3 + sizeofcmds: 288 + flags: 0x2000 + reserved: 0x0 LoadCommands: - cmd: LC_SEGMENT_64 - cmdsize: 312 + cmdsize: 232 segname: '' vmaddr: 0 - vmsize: 120 - fileoff: 448 - filesize: 120 + vmsize: 56 + fileoff: 352 + filesize: 56 maxprot: 7 initprot: 7 nsects: 2 @@ -29,57 +35,33 @@ Sections: - sectname: __text segname: __TEXT - addr: 0x0000000000000000 + addr: 0x0 size: 18 - offset: 0x000001C0 + offset: 0x160 align: 4 - reloff: 0x00000000 + reloff: 0x0 nreloc: 0 flags: 0x80000400 - reserved1: 0x00000000 - reserved2: 0x00000000 - reserved3: 0x00000000 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 content: 554889E5C745FC00000000B8010000005DC3 - - sectname: __eh_frame - segname: __TEXT - addr: 0x0000000000000018 - size: 64 - offset: 0x000001D8 + - sectname: __compact_unwind + segname: __LD + addr: 0x18 + size: 32 + offset: 0x178 align: 3 - reloff: 0x00000238 - nreloc: 4 - flags: 0x00000000 - reserved1: 0x00000000 - reserved2: 0x00000000 - reserved3: 0x00000000 - content: 1400000000000000017A520001781001100C0708900100002400000004000000F8FFFFFFFFFFFFFF120000000000000000410E108602430D0600000000000000 + reloff: 0x198 + nreloc: 1 + flags: 0x2000000 + reserved1: 0x0 + reserved2: 0x0 + reserved3: 0x0 + content: '0000000000000000120000000000000100000000000000000000000000000000' relocations: - - address: 0x0000001C - symbolnum: 0 - pcrel: false - length: 2 - extern: true - type: 5 - scattered: false - value: 0 - - address: 0x0000001C - symbolnum: 1 - pcrel: false - length: 2 - extern: true - type: 0 - scattered: false - value: 0 - - address: 0x00000020 - symbolnum: 1 - pcrel: false - length: 3 - extern: true - type: 5 - scattered: false - value: 0 - - address: 0x00000020 - symbolnum: 10 + - address: 0x0 + symbolnum: 8 pcrel: false length: 3 extern: true @@ -88,39 +70,72 @@ value: 0 - cmd: LC_SYMTAB cmdsize: 24 - symoff: 608 - nsyms: 11 - stroff: 784 - strsize: 72 + symoff: 416 + nsyms: 9 + stroff: 560 + strsize: 48 + - cmd: LC_BUILD_VERSION + cmdsize: 32 + platform: 1 + minos: 659200 + sdk: 0 + ntools: 1 + Tools: + - tool: 3 + version: 46596096 LinkEditData: NameList: - - n_strx: 8 ## N_STAB sym (in got) - n_type: 0x0E - n_sect: 2 + - n_strx: 8 + n_type: 0x64 ## N_SO STAB + n_sect: 0 n_desc: 0 - n_value: 24 - - n_strx: 18 - n_type: 0x0E - n_sect: 2 + n_value: 0 + - n_strx: 14 + n_type: 0x64 ## N_SO STAB + n_sect: 0 + n_desc: 0 + n_value: 0 + - n_strx: 21 + n_type: 0x66 ## N_OSO STAB + n_sect: 3 + n_desc: 1 + n_value: 1651001352 + - n_strx: 1 + n_type: 0x2E ## N_BNSYM STAB + n_sect: 1 + n_desc: 0 + n_value: 0 + - n_strx: 41 + n_type: 0x24 ## N_FUN STAB + n_sect: 1 n_desc: 0 - n_value: 48 + n_value: 0 + - n_strx: 1 + n_type: 0x24 ## N_FUN STAB + n_sect: 0 + n_desc: 0 + n_value: 18 - n_strx: 1 - n_type: 0x4E + n_type: 0x4E ## N_ENSYM STAB n_sect: 1 n_desc: 0 n_value: 18 - - n_strx: 2 ## _main - n_type: 0x0F + - n_strx: 1 + n_type: 0x64 ## N_SO STAB + n_sect: 1 + n_desc: 0 + n_value: 0 + - n_strx: 2 + n_type: 0xF n_sect: 1 n_desc: 0 n_value: 0 StringTable: - ' ' - _main - - EH_Frame1 - - func.eh - - '/Users/vyng/' - - test.cc - - '/Users/vyng/test.o' + - '/tmp/' + - test.c + - '/private/tmp/test.o' - _main + - '' ... diff --git a/lld/test/MachO/tools/generate-cfi-funcs.py b/lld/test/MachO/tools/generate-cfi-funcs.py --- a/lld/test/MachO/tools/generate-cfi-funcs.py +++ b/lld/test/MachO/tools/generate-cfi-funcs.py @@ -24,9 +24,6 @@ have_lsda = (random.random() < lsda_odds) frame_size = random.randint(4, 64) * 16 frame_offset = -random.randint(0, (frame_size/16 - 4)) * 16 - reg_count = random.randint(0, 5) - reg_combo = random.randint(0, factorial(reg_count) - 1) - regs_saved = saved_regs_combined[reg_count][reg_combo] global func_size_low, func_size_high func_size = random.randint(func_size_low, func_size_high) * 0x10 func_size_high += 1 @@ -34,13 +31,13 @@ func_size_low += 1 print("""\ -### %s regs=%d frame=%d lsda=%s size=%d +### %s frame=%d lsda=%s size=%d .section __TEXT,__text,regular,pure_instructions .p2align 4, 0x90 .globl %s %s: .cfi_startproc""" % ( - name, reg_count, frame_size, have_lsda, func_size, name, name)) + name, frame_size, have_lsda, func_size, name, name)) if have_lsda: global lsda_n lsda_n += 1 @@ -53,8 +50,6 @@ .cfi_offset %%rbp, %d movq %%rsp, %%rbp .cfi_def_cfa_register %%rbp""" % (frame_size, frame_offset + 6*8)) - for i in range(reg_count): - print(".cfi_offset %s, %d" % (regs_saved[i], frame_offset+(i*8))) print("""\ .fill %d popq %%rbp