#include "llvm/ExecutionEngine/Orc/DebuggerSupportPlugin.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/BinaryFormat/MachO.h"
#define DEBUG_TYPE "orc"
using namespace llvm;
using namespace llvm::jitlink;
using namespace llvm::orc;
static const char *SynthDebugSectionName = "__jitlink_synth_debug_object";
namespace {
struct MachO64LE {
using UIntPtr = uint64_t;
using Header = MachO::mach_header_64;
using SegmentLC = MachO::segment_command_64;
using Section = MachO::section_64;
using NList = MachO::nlist_64;
static constexpr support::endianness Endianness = support::little;
static constexpr const uint32_t Magic = MachO::MH_MAGIC_64;
static constexpr const uint32_t SegmentCmd = MachO::LC_SEGMENT_64;
};
class MachODebugObjectSynthesizerBase
: public GDBJITDebugInfoRegistrationPlugin::DebugSectionSynthesizer {
public:
static bool isDebugSection(Section &Sec) {
return Sec.getName().startswith("__DWARF,");
}
MachODebugObjectSynthesizerBase(LinkGraph &G, ExecutorAddr RegisterActionAddr)
: G(G), RegisterActionAddr(RegisterActionAddr) {}
virtual ~MachODebugObjectSynthesizerBase() = default;
Error preserveDebugSections() {
if (G.findSectionByName(SynthDebugSectionName)) {
LLVM_DEBUG({
dbgs() << "MachODebugObjectSynthesizer skipping graph " << G.getName()
<< " which contains an unexpected existing "
<< SynthDebugSectionName << " section.\n";
});
return Error::success();
}
LLVM_DEBUG({
dbgs() << "MachODebugObjectSynthesizer visiting graph " << G.getName()
<< "\n";
});
for (auto &Sec : G.sections()) {
if (!isDebugSection(Sec))
continue;
LLVM_DEBUG({
dbgs() << " Preserving debug section " << Sec.getName() << "\n";
});
SmallSet<Block *, 8> PreservedBlocks;
for (auto *Sym : Sec.symbols()) {
bool NewPreservedBlock =
PreservedBlocks.insert(&Sym->getBlock()).second;
if (NewPreservedBlock)
Sym->setLive(true);
}
for (auto *B : Sec.blocks())
if (!PreservedBlocks.count(B))
G.addAnonymousSymbol(*B, 0, 0, false, true);
}
return Error::success();
}
protected:
LinkGraph &G;
ExecutorAddr RegisterActionAddr;
};
template <typename MachOTraits>
class MachODebugObjectSynthesizer : public MachODebugObjectSynthesizerBase {
private:
class MachOStructWriter {
public:
MachOStructWriter(MutableArrayRef<char> Buffer) : Buffer(Buffer) {}
size_t getOffset() const { return Offset; }
template <typename MachOStruct> void write(MachOStruct S) {
assert(Offset + sizeof(S) <= Buffer.size() &&
"Container block overflow while constructing debug MachO");
if (MachOTraits::Endianness != support::endian::system_endianness())
MachO::swapStruct(S);
memcpy(Buffer.data() + Offset, &S, sizeof(S));
Offset += sizeof(S);
}
private:
MutableArrayRef<char> Buffer;
size_t Offset = 0;
};
public:
using MachODebugObjectSynthesizerBase::MachODebugObjectSynthesizerBase;
Error startSynthesis() override {
LLVM_DEBUG({
dbgs() << "Creating " << SynthDebugSectionName << " for " << G.getName()
<< "\n";
});
auto &SDOSec = G.createSection(SynthDebugSectionName, MemProt::Read);
struct DebugSectionInfo {
Section *Sec = nullptr;
StringRef SegName;
StringRef SecName;
uint64_t Alignment = 0;
orc::ExecutorAddr StartAddr;
uint64_t Size = 0;
};
SmallVector<DebugSectionInfo, 12> DebugSecInfos;
size_t NumSections = 0;
for (auto &Sec : G.sections()) {
if (llvm::empty(Sec.blocks()))
continue;
++NumSections;
if (isDebugSection(Sec)) {
size_t SepPos = Sec.getName().find(',');
if (SepPos > 16 || (Sec.getName().size() - (SepPos + 1) > 16)) {
LLVM_DEBUG({
dbgs() << "Skipping debug object synthesis for graph "
<< G.getName()
<< ": encountered non-standard DWARF section name \""
<< Sec.getName() << "\"\n";
});
return Error::success();
}
DebugSecInfos.push_back({&Sec, Sec.getName().substr(0, SepPos),
Sec.getName().substr(SepPos + 1), 0,
orc::ExecutorAddr(), 0});
} else {
NonDebugSections.push_back(&Sec);
SectionRange R(Sec);
if (!R.empty()) {
auto &FB = *R.getFirstBlock();
if (FB.getAlignmentOffset() != 0) {
auto Padding = G.allocateBuffer(FB.getAlignmentOffset());
memset(Padding.data(), 0, Padding.size());
G.createContentBlock(Sec, Padding,
FB.getAddress() - FB.getAlignmentOffset(),
FB.getAlignment(), 0);
}
}
}
}
size_t SectionsCmdSize =
sizeof(typename MachOTraits::Section) * NumSections;
size_t SegmentLCSize =
sizeof(typename MachOTraits::SegmentLC) + SectionsCmdSize;
size_t ContainerBlockSize =
sizeof(typename MachOTraits::Header) + SegmentLCSize;
auto ContainerBlockContent = G.allocateBuffer(ContainerBlockSize);
MachOContainerBlock = &G.createMutableContentBlock(
SDOSec, ContainerBlockContent, orc::ExecutorAddr(), 8, 0);
orc::ExecutorAddr NextBlockAddr(MachOContainerBlock->getSize());
for (auto &SI : DebugSecInfos) {
assert(!llvm::empty(SI.Sec->blocks()) && "Empty debug info section?");
LLVM_DEBUG({
dbgs() << " Appending " << SI.Sec->getName() << " ("
<< SI.Sec->blocks_size() << " block(s)) at "
<< formatv("{0:x8}", NextBlockAddr) << "\n";
});
for (auto *B : SI.Sec->blocks()) {
NextBlockAddr = alignToBlock(NextBlockAddr, *B);
B->setAddress(NextBlockAddr);
NextBlockAddr += B->getSize();
}
auto &FirstBlock = **SI.Sec->blocks().begin();
if (FirstBlock.getAlignmentOffset() != 0)
return make_error<StringError>(
"First block in " + SI.Sec->getName() +
" section has non-zero alignment offset",
inconvertibleErrorCode());
if (FirstBlock.getAlignment() > std::numeric_limits<uint32_t>::max())
return make_error<StringError>("First block in " + SI.Sec->getName() +
" has alignment >4Gb",
inconvertibleErrorCode());
SI.Alignment = FirstBlock.getAlignment();
SI.StartAddr = FirstBlock.getAddress();
SI.Size = NextBlockAddr - SI.StartAddr;
G.mergeSections(SDOSec, *SI.Sec);
SI.Sec = nullptr;
}
size_t DebugSectionsSize =
NextBlockAddr - orc::ExecutorAddr(MachOContainerBlock->getSize());
MachOStructWriter Writer(MachOContainerBlock->getAlreadyMutableContent());
typename MachOTraits::Header Hdr;
memset(&Hdr, 0, sizeof(Hdr));
Hdr.magic = MachOTraits::Magic;
switch (G.getTargetTriple().getArch()) {
case Triple::x86_64:
Hdr.cputype = MachO::CPU_TYPE_X86_64;
Hdr.cpusubtype = MachO::CPU_SUBTYPE_X86_64_ALL;
break;
case Triple::aarch64:
Hdr.cputype = MachO::CPU_TYPE_ARM64;
Hdr.cpusubtype = MachO::CPU_SUBTYPE_ARM64_ALL;
break;
default:
llvm_unreachable("Unsupported architecture");
}
Hdr.filetype = MachO::MH_OBJECT;
Hdr.ncmds = 1;
Hdr.sizeofcmds = SegmentLCSize;
Hdr.flags = 0;
Writer.write(Hdr);
typename MachOTraits::SegmentLC SegLC;
memset(&SegLC, 0, sizeof(SegLC));
SegLC.cmd = MachOTraits::SegmentCmd;
SegLC.cmdsize = SegmentLCSize;
SegLC.vmaddr = ContainerBlockSize;
SegLC.vmsize = DebugSectionsSize;
SegLC.fileoff = ContainerBlockSize;
SegLC.filesize = DebugSectionsSize;
SegLC.maxprot =
MachO::VM_PROT_READ | MachO::VM_PROT_WRITE | MachO::VM_PROT_EXECUTE;
SegLC.initprot =
MachO::VM_PROT_READ | MachO::VM_PROT_WRITE | MachO::VM_PROT_EXECUTE;
SegLC.nsects = NumSections;
SegLC.flags = 0;
Writer.write(SegLC);
StringSet<> ExistingLongNames;
for (auto &SI : DebugSecInfos) {
typename MachOTraits::Section Sec;
memset(&Sec, 0, sizeof(Sec));
memcpy(Sec.sectname, SI.SecName.data(), SI.SecName.size());
memcpy(Sec.segname, SI.SegName.data(), SI.SegName.size());
Sec.addr = SI.StartAddr.getValue();
Sec.size = SI.Size;
Sec.offset = SI.StartAddr.getValue();
Sec.align = SI.Alignment;
Sec.reloff = 0;
Sec.nreloc = 0;
Sec.flags = MachO::S_ATTR_DEBUG;
Writer.write(Sec);
}
NonDebugSectionsStart = Writer.getOffset();
return Error::success();
}
Error completeSynthesisAndRegister() override {
if (!MachOContainerBlock) {
LLVM_DEBUG({
dbgs() << "Not writing MachO debug object header for " << G.getName()
<< " since createDebugSection failed\n";
});
return Error::success();
}
LLVM_DEBUG({
dbgs() << "Writing MachO debug object header for " << G.getName() << "\n";
});
MachOStructWriter Writer(
MachOContainerBlock->getAlreadyMutableContent().drop_front(
NonDebugSectionsStart));
unsigned LongSectionNameIdx = 0;
for (auto *Sec : NonDebugSections) {
size_t SepPos = Sec->getName().find(',');
StringRef SegName, SecName;
std::string CustomSecName;
if ((SepPos == StringRef::npos && Sec->getName().size() <= 16)) {
SegName = "__JITLINK_CUSTOM";
SecName = Sec->getName();
} else if (SepPos < 16 && (Sec->getName().size() - (SepPos + 1) <= 16)) {
SegName = Sec->getName().substr(0, SepPos);
SecName = Sec->getName().substr(SepPos + 1);
} else {
assert(Sec->getName().size() > 16 &&
"Short section name should have been handled above");
SegName = "__JITLINK_CUSTOM";
auto IdxStr = std::to_string(++LongSectionNameIdx);
CustomSecName = Sec->getName().substr(0, 15 - IdxStr.size()).str();
CustomSecName += ".";
CustomSecName += IdxStr;
SecName = StringRef(CustomSecName.data(), 16);
}
SectionRange R(*Sec);
if (R.getFirstBlock()->getAlignmentOffset() != 0)
return make_error<StringError>(
"While building MachO debug object for " + G.getName() +
" first block has non-zero alignment offset",
inconvertibleErrorCode());
typename MachOTraits::Section SecCmd;
memset(&SecCmd, 0, sizeof(SecCmd));
memcpy(SecCmd.sectname, SecName.data(), SecName.size());
memcpy(SecCmd.segname, SegName.data(), SegName.size());
SecCmd.addr = R.getStart().getValue();
SecCmd.size = R.getSize();
SecCmd.offset = 0;
SecCmd.align = R.getFirstBlock()->getAlignment();
SecCmd.reloff = 0;
SecCmd.nreloc = 0;
SecCmd.flags = 0;
Writer.write(SecCmd);
}
SectionRange R(MachOContainerBlock->getSection());
G.allocActions().push_back(
{cantFail(shared::WrapperFunctionCall::Create<
shared::SPSArgList<shared::SPSExecutorAddrRange>>(
RegisterActionAddr, R.getRange())),
{}});
return Error::success();
}
private:
Block *MachOContainerBlock = nullptr;
SmallVector<Section *, 16> NonDebugSections;
size_t NonDebugSectionsStart = 0;
};
}
namespace llvm {
namespace orc {
Expected<std::unique_ptr<GDBJITDebugInfoRegistrationPlugin>>
GDBJITDebugInfoRegistrationPlugin::Create(ExecutionSession &ES,
JITDylib &ProcessJD,
const Triple &TT) {
auto RegisterActionAddr =
TT.isOSBinFormatMachO()
? ES.intern("_llvm_orc_registerJITLoaderGDBAllocAction")
: ES.intern("llvm_orc_registerJITLoaderGDBAllocAction");
if (auto Addr = ES.lookup({&ProcessJD}, RegisterActionAddr))
return std::make_unique<GDBJITDebugInfoRegistrationPlugin>(
ExecutorAddr(Addr->getAddress()));
else
return Addr.takeError();
}
Error GDBJITDebugInfoRegistrationPlugin::notifyFailed(
MaterializationResponsibility &MR) {
return Error::success();
}
Error GDBJITDebugInfoRegistrationPlugin::notifyRemovingResources(
ResourceKey K) {
return Error::success();
}
void GDBJITDebugInfoRegistrationPlugin::notifyTransferringResources(
ResourceKey DstKey, ResourceKey SrcKey) {}
void GDBJITDebugInfoRegistrationPlugin::modifyPassConfig(
MaterializationResponsibility &MR, LinkGraph &LG,
PassConfiguration &PassConfig) {
if (LG.getTargetTriple().getObjectFormat() == Triple::MachO)
modifyPassConfigForMachO(MR, LG, PassConfig);
else {
LLVM_DEBUG({
dbgs() << "GDBJITDebugInfoRegistrationPlugin skipping unspported graph "
<< LG.getName() << "(triple = " << LG.getTargetTriple().str()
<< "\n";
});
}
}
void GDBJITDebugInfoRegistrationPlugin::modifyPassConfigForMachO(
MaterializationResponsibility &MR, jitlink::LinkGraph &LG,
jitlink::PassConfiguration &PassConfig) {
switch (LG.getTargetTriple().getArch()) {
case Triple::x86_64:
case Triple::aarch64:
assert(LG.getPointerSize() == 8 && "Graph has incorrect pointer size");
assert(LG.getEndianness() == support::little &&
"Graph has incorrect endianness");
break;
default:
LLVM_DEBUG({
dbgs() << "GDBJITDebugInfoRegistrationPlugin skipping unsupported "
<< "MachO graph " << LG.getName()
<< "(triple = " << LG.getTargetTriple().str()
<< ", pointer size = " << LG.getPointerSize() << ", endianness = "
<< (LG.getEndianness() == support::big ? "big" : "little")
<< ")\n";
});
return;
}
bool HasDebugSections = false;
for (auto &Sec : LG.sections())
if (MachODebugObjectSynthesizerBase::isDebugSection(Sec)) {
HasDebugSections = true;
break;
}
if (HasDebugSections) {
LLVM_DEBUG({
dbgs() << "GDBJITDebugInfoRegistrationPlugin: Graph " << LG.getName()
<< " contains debug info. Installing debugger support passes.\n";
});
auto MDOS = std::make_shared<MachODebugObjectSynthesizer<MachO64LE>>(
LG, RegisterActionAddr);
PassConfig.PrePrunePasses.push_back(
[=](LinkGraph &G) { return MDOS->preserveDebugSections(); });
PassConfig.PostPrunePasses.push_back(
[=](LinkGraph &G) { return MDOS->startSynthesis(); });
PassConfig.PreFixupPasses.push_back(
[=](LinkGraph &G) { return MDOS->completeSynthesisAndRegister(); });
} else {
LLVM_DEBUG({
dbgs() << "GDBJITDebugInfoRegistrationPlugin: Graph " << LG.getName()
<< " contains no debug info. Skipping.\n";
});
}
}
} }