[ORC][ORC-RT] Add support for callback-based lookup of JIT'd MachO unwind info.

In LLVM the MachOPlatform class is modified to identify unwind info sections
and the address ranges of the functions these sections cover. These address
ranges are then communicated to the ORC runtime by attaching them to the
register-object-platform-sections allocation action.

In the ORC runtime the unwind-info section addresses are recorded and used to
support lookup of unwind info via the new `findDynamicUnwindSections` function.
At bootstrap time the ORC runtime checks for the presence of new
unwind-info-lookup-registration functions in libunwind (see
https://reviews.llvm.org/D142176), and if available uses them to register the
`findDynamicUnwindSections` function with libunwind to enable callback-based
lookup. If the new unwind-info-lookup-registration functions are not available
then the ORC runtime falls back to using the existing libunwind registration
APIs.

The callback-based scheme is intended to address three shortcomings in the
current registration scheme for JIT'd unwind info on Darwin: (1) Lack of
compact-unwind support, (2) inability to describe the subarchitecture of JIT'd
frames, and (3) lack of efficient address-based lookup data structures in
libunwind.

For more details see the proposed libunwind changes in
https://reviews.llvm.org/D142176.
This commit is contained in:
Lang Hames 2023-01-19 21:33:41 -08:00
parent 79b917b6e8
commit 3507df9c20
3 changed files with 304 additions and 30 deletions

View File

@ -71,6 +71,26 @@ extern "C" void swift_registerTypeMetadataRecords(
const TypeMetadataRecord *begin,
const TypeMetadataRecord *end) ORC_RT_WEAK_IMPORT;
// Libunwind prototypes.
struct unw_dynamic_unwind_sections {
uintptr_t dso_base;
uintptr_t dwarf_section;
size_t dwarf_section_length;
uintptr_t compact_unwind_section;
size_t compact_unwind_section_length;
};
typedef int (*unw_find_dynamic_unwind_sections)(
uintptr_t addr, struct unw_dynamic_unwind_sections *info);
extern "C" int __unw_add_find_dynamic_unwind_sections(
unw_find_dynamic_unwind_sections find_dynamic_unwind_sections)
ORC_RT_WEAK_IMPORT;
extern "C" int __unw_remove_find_dynamic_unwind_sections(
unw_find_dynamic_unwind_sections find_dynamic_unwind_sections)
ORC_RT_WEAK_IMPORT;
namespace {
struct MachOJITDylibDepInfo {
@ -111,6 +131,35 @@ public:
}
};
struct UnwindSectionInfo {
std::vector<ExecutorAddrRange> CodeRanges;
ExecutorAddrRange DwarfSection;
ExecutorAddrRange CompactUnwindSection;
};
using SPSUnwindSectionInfo =
SPSTuple<SPSSequence<SPSExecutorAddrRange>, SPSExecutorAddrRange,
SPSExecutorAddrRange>;
template <>
class SPSSerializationTraits<SPSUnwindSectionInfo, UnwindSectionInfo> {
public:
static size_t size(const UnwindSectionInfo &USI) {
return SPSUnwindSectionInfo::AsArgList::size(
USI.CodeRanges, USI.DwarfSection, USI.CompactUnwindSection);
}
static bool serialize(SPSOutputBuffer &OB, const UnwindSectionInfo &USI) {
return SPSUnwindSectionInfo::AsArgList::serialize(
OB, USI.CodeRanges, USI.DwarfSection, USI.CompactUnwindSection);
}
static bool deserialize(SPSInputBuffer &IB, UnwindSectionInfo &USI) {
return SPSUnwindSectionInfo::AsArgList::deserialize(
IB, USI.CodeRanges, USI.DwarfSection, USI.CompactUnwindSection);
}
};
} // namespace __orc_rt
namespace {
@ -216,6 +265,18 @@ private:
std::vector<span<RecordElement>> New;
};
struct UnwindSections {
UnwindSections(const UnwindSectionInfo &USI)
: DwarfSection(USI.DwarfSection.toSpan<char>()),
CompactUnwindSection(USI.CompactUnwindSection.toSpan<char>()) {}
span<char> DwarfSection;
span<char> CompactUnwindSection;
};
using UnwindSectionsMap =
IntervalMap<char *, UnwindSections, IntervalCoalescing::Disabled>;
struct JITDylibState {
std::string Name;
void *Header = nullptr;
@ -227,6 +288,7 @@ private:
const objc_image_info *ObjCImageInfo = nullptr;
std::unordered_map<void *, std::vector<char>> DataSectionContent;
std::unordered_map<void *, size_t> ZeroInitRanges;
UnwindSectionsMap UnwindSections;
RecordSectionsTracker<void (*)()> ModInitsSections;
RecordSectionsTracker<void *> ObjCClassListSections;
RecordSectionsTracker<void *> ObjCSelRefsSections;
@ -240,9 +302,9 @@ private:
};
public:
static void initialize();
static Error create();
static MachOPlatformRuntimeState &get();
static void destroy();
static Error destroy();
MachOPlatformRuntimeState() = default;
@ -253,15 +315,18 @@ public:
MachOPlatformRuntimeState(MachOPlatformRuntimeState &&) = delete;
MachOPlatformRuntimeState &operator=(MachOPlatformRuntimeState &&) = delete;
Error initialize();
Error shutdown();
Error registerJITDylib(std::string Name, void *Header);
Error deregisterJITDylib(void *Header);
Error registerThreadDataSection(span<const char> ThreadDataSection);
Error deregisterThreadDataSection(span<const char> ThreadDataSection);
Error registerObjectPlatformSections(
ExecutorAddr HeaderAddr,
ExecutorAddr HeaderAddr, std::optional<UnwindSectionInfo> UnwindSections,
std::vector<std::pair<std::string_view, ExecutorAddrRange>> Secs);
Error deregisterObjectPlatformSections(
ExecutorAddr HeaderAddr,
ExecutorAddr HeaderAddr, std::optional<UnwindSectionInfo> UnwindSections,
std::vector<std::pair<std::string_view, ExecutorAddrRange>> Secs);
const char *dlerror();
@ -285,6 +350,10 @@ private:
Expected<ExecutorAddr> lookupSymbolInJITDylib(void *DSOHandle,
std::string_view Symbol);
bool lookupUnwindSections(void *Addr, unw_dynamic_unwind_sections &Info);
static int findDynamicUnwindSections(uintptr_t addr,
unw_dynamic_unwind_sections *info);
static Error registerEHFrames(span<const char> EHFrameSection);
static Error deregisterEHFrames(span<const char> EHFrameSection);
@ -308,6 +377,8 @@ private:
static MachOPlatformRuntimeState *MOPS;
bool UseCallbackStyleUnwindInfo = false;
// FIXME: Move to thread-state.
std::string DLFcnError;
@ -327,9 +398,10 @@ private:
MachOPlatformRuntimeState *MachOPlatformRuntimeState::MOPS = nullptr;
void MachOPlatformRuntimeState::initialize() {
Error MachOPlatformRuntimeState::create() {
assert(!MOPS && "MachOPlatformRuntimeState should be null");
MOPS = new MachOPlatformRuntimeState();
return MOPS->initialize();
}
MachOPlatformRuntimeState &MachOPlatformRuntimeState::get() {
@ -337,9 +409,42 @@ MachOPlatformRuntimeState &MachOPlatformRuntimeState::get() {
return *MOPS;
}
void MachOPlatformRuntimeState::destroy() {
Error MachOPlatformRuntimeState::destroy() {
assert(MOPS && "MachOPlatformRuntimeState not initialized");
auto Err = MOPS->shutdown();
delete MOPS;
return Err;
}
Error MachOPlatformRuntimeState::initialize() {
UseCallbackStyleUnwindInfo = __unw_add_find_dynamic_unwind_sections &&
__unw_remove_find_dynamic_unwind_sections;
if (UseCallbackStyleUnwindInfo) {
ORC_RT_DEBUG({
printdbg("__unw_add/remove_find_dynamic_unwind_sections available."
" Using callback-based frame info lookup.\n");
});
if (__unw_add_find_dynamic_unwind_sections(&findDynamicUnwindSections))
return make_error<StringError>(
"Could not register findDynamicUnwindSections");
} else {
ORC_RT_DEBUG({
printdbg("__unw_add/remove_find_dynamic_unwind_sections not available."
" Using classic frame info registration.\n");
});
}
return Error::success();
}
Error MachOPlatformRuntimeState::shutdown() {
if (__unw_add_find_dynamic_unwind_sections &&
__unw_remove_find_dynamic_unwind_sections) {
if (__unw_remove_find_dynamic_unwind_sections(&findDynamicUnwindSections)) {
ORC_RT_DEBUG(
{ printdbg("__unw_remove_find_dynamic_unwind_sections failed.\n"); });
}
}
return Error::success();
}
Error MachOPlatformRuntimeState::registerJITDylib(std::string Name,
@ -419,7 +524,7 @@ Error MachOPlatformRuntimeState::deregisterThreadDataSection(
}
Error MachOPlatformRuntimeState::registerObjectPlatformSections(
ExecutorAddr HeaderAddr,
ExecutorAddr HeaderAddr, std::optional<UnwindSectionInfo> UnwindInfo,
std::vector<std::pair<std::string_view, ExecutorAddrRange>> Secs) {
// FIXME: Reject platform section registration after the JITDylib is
@ -440,11 +545,35 @@ Error MachOPlatformRuntimeState::registerObjectPlatformSections(
return make_error<StringError>(ErrStream.str());
}
if (UnwindInfo && UseCallbackStyleUnwindInfo) {
ORC_RT_DEBUG({
printdbg(" Registering new-style unwind info for:\n"
" DWARF: %p -- %p\n"
" Compact-unwind: %p -- %p\n"
" for:\n",
UnwindInfo->DwarfSection.Start.toPtr<void *>(),
UnwindInfo->DwarfSection.End.toPtr<void *>(),
UnwindInfo->CompactUnwindSection.Start.toPtr<void *>(),
UnwindInfo->CompactUnwindSection.End.toPtr<void *>());
});
for (auto &CodeRange : UnwindInfo->CodeRanges) {
JDS->UnwindSections.insert(CodeRange.Start.toPtr<char *>(),
CodeRange.End.toPtr<char *>(), *UnwindInfo);
ORC_RT_DEBUG({
printdbg(" [ %p -- %p ]\n", CodeRange.Start.toPtr<void *>(),
CodeRange.End.toPtr<void *>());
});
}
}
for (auto &KV : Secs) {
// FIXME: Validate section ranges?
if (KV.first == "__TEXT,__eh_frame") {
if (auto Err = registerEHFrames(KV.second.toSpan<const char>()))
return Err;
if (!UseCallbackStyleUnwindInfo) {
// Use classic libunwind registration.
if (auto Err = registerEHFrames(KV.second.toSpan<const char>()))
return Err;
}
} else if (KV.first == "__DATA,__data") {
assert(!JDS->DataSectionContent.count(KV.second.Start.toPtr<char *>()) &&
"Address already registered.");
@ -483,7 +612,7 @@ Error MachOPlatformRuntimeState::registerObjectPlatformSections(
}
Error MachOPlatformRuntimeState::deregisterObjectPlatformSections(
ExecutorAddr HeaderAddr,
ExecutorAddr HeaderAddr, std::optional<UnwindSectionInfo> UnwindInfo,
std::vector<std::pair<std::string_view, ExecutorAddrRange>> Secs) {
// TODO: Make this more efficient? (maybe unnecessary if removal is rare?)
// TODO: Add a JITDylib prepare-for-teardown operation that clears all
@ -510,11 +639,35 @@ Error MachOPlatformRuntimeState::deregisterObjectPlatformSections(
// any Swift or ObjC. Once this happens we can clear (and no longer record)
// data section content, as the library could never be re-initialized.
if (UnwindInfo && UseCallbackStyleUnwindInfo) {
ORC_RT_DEBUG({
printdbg(" Deregistering new-style unwind info for:\n"
" DWARF: %p -- %p\n"
" Compact-unwind: %p -- %p\n"
" for:\n",
UnwindInfo->DwarfSection.Start.toPtr<void *>(),
UnwindInfo->DwarfSection.End.toPtr<void *>(),
UnwindInfo->CompactUnwindSection.Start.toPtr<void *>(),
UnwindInfo->CompactUnwindSection.End.toPtr<void *>());
});
for (auto &CodeRange : UnwindInfo->CodeRanges) {
JDS->UnwindSections.erase(CodeRange.Start.toPtr<char *>(),
CodeRange.End.toPtr<char *>());
ORC_RT_DEBUG({
printdbg(" [ %p -- %p ]\n", CodeRange.Start.toPtr<void *>(),
CodeRange.End.toPtr<void *>());
});
}
}
for (auto &KV : Secs) {
// FIXME: Validate section ranges?
if (KV.first == "__TEXT,__eh_frame") {
if (auto Err = deregisterEHFrames(KV.second.toSpan<const char>()))
return Err;
if (!UseCallbackStyleUnwindInfo) {
// Use classic libunwind registration.
if (auto Err = deregisterEHFrames(KV.second.toSpan<const char>()))
return Err;
}
} else if (KV.first == "__DATA,__data") {
JDS->DataSectionContent.erase(KV.second.Start.toPtr<char *>());
} else if (KV.first == "__DATA,__common") {
@ -710,6 +863,37 @@ void walkEHFrameSection(span<const char> EHFrameSection,
}
}
bool MachOPlatformRuntimeState::lookupUnwindSections(
void *Addr, unw_dynamic_unwind_sections &Info) {
ORC_RT_DEBUG(
{ printdbg("Tried to lookup unwind-info via new lookup call.\n"); });
std::lock_guard<std::mutex> Lock(JDStatesMutex);
for (auto &KV : JDStates) {
auto &JD = KV.second;
auto I = JD.UnwindSections.find(reinterpret_cast<char *>(Addr));
if (I != JD.UnwindSections.end()) {
Info.dso_base = reinterpret_cast<uintptr_t>(JD.Header);
Info.dwarf_section =
reinterpret_cast<uintptr_t>(I->second.DwarfSection.data());
Info.dwarf_section_length = I->second.DwarfSection.size();
Info.compact_unwind_section =
reinterpret_cast<uintptr_t>(I->second.CompactUnwindSection.data());
Info.compact_unwind_section_length =
I->second.CompactUnwindSection.size();
return true;
}
}
return false;
}
int MachOPlatformRuntimeState::findDynamicUnwindSections(
uintptr_t addr, unw_dynamic_unwind_sections *info) {
if (!info)
return 0;
return MachOPlatformRuntimeState::get().lookupUnwindSections((void *)addr,
*info);
}
Error MachOPlatformRuntimeState::registerEHFrames(
span<const char> EHFrameSection) {
walkEHFrameSection(EHFrameSection, __register_frame);
@ -1101,10 +1285,7 @@ ORC_RT_INTERFACE __orc_rt_CWrapperFunctionResult
__orc_rt_macho_platform_bootstrap(char *ArgData, size_t ArgSize) {
return WrapperFunction<SPSError()>::handle(
ArgData, ArgSize,
[]() -> Error {
MachOPlatformRuntimeState::initialize();
return Error::success();
})
[]() { return MachOPlatformRuntimeState::create(); })
.release();
}
@ -1112,10 +1293,7 @@ ORC_RT_INTERFACE __orc_rt_CWrapperFunctionResult
__orc_rt_macho_platform_shutdown(char *ArgData, size_t ArgSize) {
return WrapperFunction<SPSError()>::handle(
ArgData, ArgSize,
[]() -> Error {
MachOPlatformRuntimeState::destroy();
return Error::success();
})
[]() { return MachOPlatformRuntimeState::destroy(); })
.release();
}
@ -1145,13 +1323,15 @@ ORC_RT_INTERFACE __orc_rt_CWrapperFunctionResult
__orc_rt_macho_register_object_platform_sections(char *ArgData,
size_t ArgSize) {
return WrapperFunction<SPSError(SPSExecutorAddr,
SPSOptional<SPSUnwindSectionInfo>,
SPSMachOObjectPlatformSectionsMap)>::
handle(ArgData, ArgSize,
[](ExecutorAddr HeaderAddr,
[](ExecutorAddr HeaderAddr, std::optional<UnwindSectionInfo> USI,
std::vector<std::pair<std::string_view, ExecutorAddrRange>>
&Secs) {
return MachOPlatformRuntimeState::get()
.registerObjectPlatformSections(HeaderAddr, std::move(Secs));
.registerObjectPlatformSections(HeaderAddr, std::move(USI),
std::move(Secs));
})
.release();
}
@ -1160,13 +1340,14 @@ ORC_RT_INTERFACE __orc_rt_CWrapperFunctionResult
__orc_rt_macho_deregister_object_platform_sections(char *ArgData,
size_t ArgSize) {
return WrapperFunction<SPSError(SPSExecutorAddr,
SPSOptional<SPSUnwindSectionInfo>,
SPSMachOObjectPlatformSectionsMap)>::
handle(ArgData, ArgSize,
[](ExecutorAddr HeaderAddr,
[](ExecutorAddr HeaderAddr, std::optional<UnwindSectionInfo> USI,
std::vector<std::pair<std::string_view, ExecutorAddrRange>>
&Secs) {
return MachOPlatformRuntimeState::get()
.deregisterObjectPlatformSections(HeaderAddr,
.deregisterObjectPlatformSections(HeaderAddr, std::move(USI),
std::move(Secs));
})
.release();

View File

@ -147,6 +147,12 @@ private:
using InitSymbolDepMap =
DenseMap<MaterializationResponsibility *, JITLinkSymbolSet>;
struct UnwindSections {
SmallVector<ExecutorAddrRange> CodeRanges;
ExecutorAddrRange DwarfSection;
ExecutorAddrRange CompactUnwindSection;
};
Error bootstrapPipelineStart(jitlink::LinkGraph &G);
Error bootstrapPipelineRecordRuntimeFunctions(jitlink::LinkGraph &G);
Error bootstrapPipelineEnd(jitlink::LinkGraph &G);
@ -164,6 +170,8 @@ private:
Error fixTLVSectionsAndEdges(jitlink::LinkGraph &G, JITDylib &JD);
std::optional<UnwindSections> findUnwindSectionInfo(jitlink::LinkGraph &G);
Error registerObjectPlatformSections(jitlink::LinkGraph &G, JITDylib &JD,
bool InBootstrapPhase);

View File

@ -249,6 +249,7 @@ private:
StringRef DataCommonSectionName = "__DATA,__common";
StringRef DataDataSectionName = "__DATA,__data";
StringRef EHFrameSectionName = "__TEXT,__eh_frame";
StringRef CompactUnwindInfoSectionName = "__TEXT,__unwind_info";
StringRef ModInitFuncSectionName = "__DATA,__mod_init_func";
StringRef ObjCClassListSectionName = "__DATA,__objc_classlist";
StringRef ObjCImageInfoSectionName = "__DATA,__objc_image_info";
@ -1068,6 +1069,81 @@ Error MachOPlatform::MachOPlatformPlugin::fixTLVSectionsAndEdges(
return Error::success();
}
std::optional<MachOPlatform::MachOPlatformPlugin::UnwindSections>
MachOPlatform::MachOPlatformPlugin::findUnwindSectionInfo(
jitlink::LinkGraph &G) {
using namespace jitlink;
UnwindSections US;
// ScanSection records a section range and adds any executable blocks that
// that section points to to the CodeBlocks vector.
SmallVector<Block *> CodeBlocks;
auto ScanUnwindInfoSection = [&](Section &Sec, ExecutorAddrRange &SecRange) {
if (Sec.blocks().empty())
return;
SecRange = (*Sec.blocks().begin())->getRange();
for (auto *B : Sec.blocks()) {
auto R = B->getRange();
SecRange.Start = std::min(SecRange.Start, R.Start);
SecRange.End = std::max(SecRange.End, R.End);
for (auto &E : B->edges()) {
if (!E.getTarget().isDefined())
continue;
auto &TargetBlock = E.getTarget().getBlock();
auto &TargetSection = TargetBlock.getSection();
if ((TargetSection.getMemProt() & MemProt::Exec) == MemProt::Exec)
CodeBlocks.push_back(&TargetBlock);
}
}
};
if (Section *EHFrameSec = G.findSectionByName(EHFrameSectionName))
ScanUnwindInfoSection(*EHFrameSec, US.DwarfSection);
if (Section *CUInfoSec = G.findSectionByName(CompactUnwindInfoSectionName))
ScanUnwindInfoSection(*CUInfoSec, US.CompactUnwindSection);
// If we didn't find any pointed-to code-blocks then there's no need to
// register any info.
if (CodeBlocks.empty())
return std::nullopt;
// We have info to register. Sort the code blocks into address order and
// build a list of contiguous address ranges covering them all.
llvm::sort(CodeBlocks, [](const Block *LHS, const Block *RHS) {
return LHS->getAddress() < RHS->getAddress();
});
for (auto *B : CodeBlocks) {
if (US.CodeRanges.empty() || US.CodeRanges.back().End != B->getAddress())
US.CodeRanges.push_back(B->getRange());
else
US.CodeRanges.back().End = B->getRange().End;
}
LLVM_DEBUG({
dbgs() << "MachOPlatform identified unwind info in " << G.getName() << ":\n"
<< " DWARF: ";
if (US.DwarfSection.Start)
dbgs() << US.DwarfSection << "\n";
else
dbgs() << "none\n";
dbgs() << " Compact-unwind: ";
if (US.CompactUnwindSection.Start)
dbgs() << US.CompactUnwindSection << "\n";
else
dbgs() << "none\n"
<< "for code ranges:\n";
for (auto &CR : US.CodeRanges)
dbgs() << " " << CR << "\n";
if (US.CodeRanges.size() >= G.sections_size())
dbgs() << "WARNING: High number of discontiguous code ranges! "
"Padding may be interfering with coalescing.\n";
});
return US;
}
Error MachOPlatform::MachOPlatformPlugin::registerObjectPlatformSections(
jitlink::LinkGraph &G, JITDylib &JD, bool InBootstrapPhase) {
@ -1128,7 +1204,14 @@ Error MachOPlatform::MachOPlatformPlugin::registerObjectPlatformSections(
MachOPlatformSecs.push_back({SecName, R.getRange()});
}
if (!MachOPlatformSecs.empty()) {
std::optional<std::tuple<SmallVector<ExecutorAddrRange>, ExecutorAddrRange,
ExecutorAddrRange>>
UnwindInfo;
if (auto UI = findUnwindSectionInfo(G))
UnwindInfo = std::make_tuple(std::move(UI->CodeRanges), UI->DwarfSection,
UI->CompactUnwindSection);
if (!MachOPlatformSecs.empty() || UnwindInfo) {
ExecutorAddr HeaderAddr;
{
std::lock_guard<std::mutex> Lock(MP.PlatformMutex);
@ -1145,9 +1228,11 @@ Error MachOPlatform::MachOPlatformPlugin::registerObjectPlatformSections(
dbgs() << " " << KV.first << ": " << KV.second << "\n";
});
using SPSRegisterObjectPlatformSectionsArgs =
SPSArgList<SPSExecutorAddr,
SPSSequence<SPSTuple<SPSString, SPSExecutorAddrRange>>>;
using SPSRegisterObjectPlatformSectionsArgs = SPSArgList<
SPSExecutorAddr,
SPSOptional<SPSTuple<SPSSequence<SPSExecutorAddrRange>,
SPSExecutorAddrRange, SPSExecutorAddrRange>>,
SPSSequence<SPSTuple<SPSString, SPSExecutorAddrRange>>>;
shared::AllocActions &allocActions = LLVM_LIKELY(!InBootstrapPhase)
? G.allocActions()
@ -1156,12 +1241,12 @@ Error MachOPlatform::MachOPlatformPlugin::registerObjectPlatformSections(
allocActions.push_back(
{cantFail(
WrapperFunctionCall::Create<SPSRegisterObjectPlatformSectionsArgs>(
MP.RegisterObjectPlatformSections.Addr, HeaderAddr,
MP.RegisterObjectPlatformSections.Addr, HeaderAddr, UnwindInfo,
MachOPlatformSecs)),
cantFail(
WrapperFunctionCall::Create<SPSRegisterObjectPlatformSectionsArgs>(
MP.DeregisterObjectPlatformSections.Addr, HeaderAddr,
MachOPlatformSecs))});
UnwindInfo, MachOPlatformSecs))});
}
return Error::success();