summaryrefslogtreecommitdiff
path: root/libunwind
diff options
context:
space:
mode:
authorLang Hames <lhames@gmail.com>2023-01-19 20:45:05 -0800
committerLang Hames <lhames@gmail.com>2023-02-10 14:36:25 -0800
commit0751fc68b976d25dd3041217dad16622cf151cd6 (patch)
treed8f3c8e24d0c7137350bc17055302fb914c9e13c /libunwind
parent89197b59f597995ecb6b6dc2e262a1ecddeee901 (diff)
downloadllvm-0751fc68b976d25dd3041217dad16622cf151cd6.tar.gz
[libunwind] On Darwin, add a callback-based lookup scheme for JIT'd unwind info.
This commit adds support for a new callback-based lookup scheme for unwind info that was inspired by the `_dyld_find_unwind_info_sections` SPI that libunwind uses to find unwind-info in non-JIT'd frames. From llvm-project/libunwind/src/AddressSpace.hpp: ``` struct dyld_unwind_sections { const struct mach_header* mh; const void* dwarf_section; uintptr_t dwarf_section_length; const void* compact_unwind_section; uintptr_t compact_unwind_section_length; }; extern bool _dyld_find_unwind_sections(void *, dyld_unwind_sections *); ``` During unwinding libunwind calls `_dyld_find_unwind_sections` to both find unwind section addresses and identify the subarchitecture for frames (via the MachO-header pointed to by the mh field). This commit introduces two new libunwind SPI functions: ``` struct unw_dynamic_unwind_sections { unw_word_t dso_base; unw_word_t dwarf_section; size_t dwarf_section_length; unw_word_t compact_unwind_section; size_t compact_unwind_section_length; }; typedef int (*unw_find_dynamic_unwind_sections)( unw_word_t addr, struct unw_dynamic_unwind_sections *info); // Returns UNW_ESUCCESS if successfully registered, UNW_EINVAL for duplicate // registrations, and UNW_ENOMEM to indicate too many registrations. extern int __unw_add_find_dynamic_unwind_sections( unw_find_dynamic_unwind_sections find_dynamic_unwind_sections); // Returns UNW_ESUCCESS if successfully deregistered, UNW_EINVAL to indicate // no such registration. extern int __unw_remove_find_dynamic_unwind_sections( unw_find_dynamic_unwind_sections find_dynamic_unwind_sections); ``` These can be used to register and deregister callbacks that have a similar signature to `_dyld_find_unwind_sections`. During unwinding if `_dyld_find_unwind_sections` returns false (indicating that no frame info was found by dyld) then registered callbacks are run in registration order until either the unwind info is found or the end of the list is reached. With this commit, and by implementing the find-unwind-info callback in the ORC runtime in LLVM, we (1) enable support for registering JIT'd compact-unwind info with libunwind*, (2) provide a way to identify the subarchitecture for each frame (by returning a pointer to a JIT'd MachO header), and (3) delegate tracking of unwind info to the callback, which may be able to implement more efficient address-based lookup than libunwind. * JITLink does not process or register compact unwind info yet, so this patch does not fully enable compact unwind info in ORC, it simply provides some necessary plumbing. JITLink support for compact unwind should land some time in the LLVM 17 development cycle. Reviewed By: pete Differential Revision: https://reviews.llvm.org/D142176
Diffstat (limited to 'libunwind')
-rw-r--r--libunwind/src/AddressSpace.hpp20
-rw-r--r--libunwind/src/libunwind.cpp80
-rw-r--r--libunwind/src/libunwind_ext.h65
3 files changed, 165 insertions, 0 deletions
diff --git a/libunwind/src/AddressSpace.hpp b/libunwind/src/AddressSpace.hpp
index 26d289068b38..1abbc8225468 100644
--- a/libunwind/src/AddressSpace.hpp
+++ b/libunwind/src/AddressSpace.hpp
@@ -66,6 +66,10 @@ char *getFuncNameFromTBTable(uintptr_t pc, uint16_t &NameLen,
// In 10.7.0 or later, libSystem.dylib implements this function.
extern "C" bool _dyld_find_unwind_sections(void *, dyld_unwind_sections *);
+namespace libunwind {
+ bool findDynamicUnwindSections(void *, unw_dynamic_unwind_sections *);
+}
+
#elif defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) && defined(_LIBUNWIND_IS_BAREMETAL)
// When statically linked on bare-metal, the symbols for the EH table are looked
@@ -497,6 +501,22 @@ inline bool LocalAddressSpace::findUnwindSections(pint_t targetAddr,
info.compact_unwind_section_length = (size_t)dyldInfo.compact_unwind_section_length;
return true;
}
+
+ unw_dynamic_unwind_sections dynamicUnwindSectionInfo;
+ if (findDynamicUnwindSections((void *)targetAddr,
+ &dynamicUnwindSectionInfo)) {
+ info.dso_base = dynamicUnwindSectionInfo.dso_base;
+#if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND)
+ info.dwarf_section = (uintptr_t)dynamicUnwindSectionInfo.dwarf_section;
+ info.dwarf_section_length = dynamicUnwindSectionInfo.dwarf_section_length;
+#endif
+ info.compact_unwind_section =
+ (uintptr_t)dynamicUnwindSectionInfo.compact_unwind_section;
+ info.compact_unwind_section_length =
+ dynamicUnwindSectionInfo.compact_unwind_section_length;
+ return true;
+ }
+
#elif defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) && defined(_LIBUNWIND_IS_BAREMETAL)
info.dso_base = 0;
// Bare metal is statically linked, so no need to ask the dynamic loader
diff --git a/libunwind/src/libunwind.cpp b/libunwind/src/libunwind.cpp
index 0faea2b78570..1bd18659b786 100644
--- a/libunwind/src/libunwind.cpp
+++ b/libunwind/src/libunwind.cpp
@@ -349,7 +349,87 @@ void __unw_remove_dynamic_eh_frame_section(unw_word_t eh_frame_start) {
#endif // defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND)
#endif // !defined(__USING_SJLJ_EXCEPTIONS__)
+#ifdef __APPLE__
+namespace libunwind {
+
+static constexpr size_t MAX_DYNAMIC_UNWIND_SECTIONS_FINDERS = 8;
+
+static RWMutex findDynamicUnwindSectionsLock;
+static size_t numDynamicUnwindSectionsFinders = 0;
+static unw_find_dynamic_unwind_sections
+ dynamicUnwindSectionsFinders[MAX_DYNAMIC_UNWIND_SECTIONS_FINDERS] = {0};
+
+bool findDynamicUnwindSections(void *addr, unw_dynamic_unwind_sections *info) {
+ bool found = false;
+ findDynamicUnwindSectionsLock.lock_shared();
+ for (size_t i = 0; i != numDynamicUnwindSectionsFinders; ++i) {
+ if (dynamicUnwindSectionsFinders[i]((unw_word_t)addr, info)) {
+ found = true;
+ break;
+ }
+ }
+ findDynamicUnwindSectionsLock.unlock_shared();
+ return found;
+}
+
+} // namespace libunwind
+
+int __unw_add_find_dynamic_unwind_sections(
+ unw_find_dynamic_unwind_sections find_dynamic_unwind_sections) {
+ findDynamicUnwindSectionsLock.lock();
+
+ // Check that we have enough space...
+ if (numDynamicUnwindSectionsFinders == MAX_DYNAMIC_UNWIND_SECTIONS_FINDERS) {
+ findDynamicUnwindSectionsLock.unlock();
+ return UNW_ENOMEM;
+ }
+
+ // Check for value already present...
+ for (size_t i = 0; i != numDynamicUnwindSectionsFinders; ++i) {
+ if (dynamicUnwindSectionsFinders[i] == find_dynamic_unwind_sections) {
+ findDynamicUnwindSectionsLock.unlock();
+ return UNW_EINVAL;
+ }
+ }
+
+ // Success -- add callback entry.
+ dynamicUnwindSectionsFinders[numDynamicUnwindSectionsFinders++] =
+ find_dynamic_unwind_sections;
+ findDynamicUnwindSectionsLock.unlock();
+
+ return UNW_ESUCCESS;
+}
+
+int __unw_remove_find_dynamic_unwind_sections(
+ unw_find_dynamic_unwind_sections find_dynamic_unwind_sections) {
+ findDynamicUnwindSectionsLock.lock();
+
+ // Find index to remove.
+ size_t finderIdx = numDynamicUnwindSectionsFinders;
+ for (size_t i = 0; i != numDynamicUnwindSectionsFinders; ++i) {
+ if (dynamicUnwindSectionsFinders[i] == find_dynamic_unwind_sections) {
+ finderIdx = i;
+ break;
+ }
+ }
+
+ // If no such registration is present then error out.
+ if (finderIdx == numDynamicUnwindSectionsFinders) {
+ findDynamicUnwindSectionsLock.unlock();
+ return UNW_EINVAL;
+ }
+
+ // Remove entry.
+ for (size_t i = finderIdx; i != numDynamicUnwindSectionsFinders - 1; ++i)
+ dynamicUnwindSectionsFinders[i] = dynamicUnwindSectionsFinders[i + 1];
+ dynamicUnwindSectionsFinders[--numDynamicUnwindSectionsFinders] = nullptr;
+
+ findDynamicUnwindSectionsLock.unlock();
+ return UNW_ESUCCESS;
+}
+
+#endif // __APPLE__
// Add logging hooks in Debug builds only
#ifndef NDEBUG
diff --git a/libunwind/src/libunwind_ext.h b/libunwind/src/libunwind_ext.h
index fdc533cb8962..28db43a4f6ee 100644
--- a/libunwind/src/libunwind_ext.h
+++ b/libunwind/src/libunwind_ext.h
@@ -58,6 +58,71 @@ extern void __unw_remove_dynamic_fde(unw_word_t fde);
extern void __unw_add_dynamic_eh_frame_section(unw_word_t eh_frame_start);
extern void __unw_remove_dynamic_eh_frame_section(unw_word_t eh_frame_start);
+#ifdef __APPLE__
+
+// Holds a description of the object-format-header (if any) and unwind info
+// sections for a given address:
+//
+// * dso_base should point to a header for the JIT'd object containing the
+// given address. The header's type should match the format type that
+// libunwind was compiled for (so a mach_header or mach_header_64 on Darwin).
+// A value of zero indicates that no such header exists.
+//
+// * dwarf_section and dwarf_section_length hold the address range of a DWARF
+// eh-frame section associated with the given address, if any. If the
+// dwarf_section_length field is zero it indicates that no such section
+// exists (and in this case dwarf_section should also be set to zero).
+//
+// * compact_unwind_section and compact_unwind_section_length hold the address
+// range of a compact-unwind info section associated with the given address,
+// if any. If the compact_unwind_section_length field is zero it indicates
+// that no such section exists (and in this case compact_unwind_section
+// should also be set to zero).
+//
+// See the unw_find_dynamic_unwind_sections type below for more details.
+struct unw_dynamic_unwind_sections {
+ unw_word_t dso_base;
+ unw_word_t dwarf_section;
+ size_t dwarf_section_length;
+ unw_word_t compact_unwind_section;
+ size_t compact_unwind_section_length;
+};
+
+// Typedef for unwind-info lookup callbacks. Functions of this type can be
+// registered and deregistered using __unw_add_find_dynamic_unwind_sections
+// and __unw_remove_find_dynamic_unwind_sections respectively.
+//
+// An unwind-info lookup callback should return 1 to indicate that it found
+// unwind-info for the given address, or 0 to indicate that it did not find
+// unwind-info for the given address. If found, the callback should populate
+// some or all of the fields of the info argument (which is guaranteed to be
+// non-null with all fields zero-initialized):
+typedef int (*unw_find_dynamic_unwind_sections)(
+ unw_word_t addr, struct unw_dynamic_unwind_sections *info);
+
+// Register a dynamic unwind-info lookup callback. If libunwind does not find
+// unwind info for a given frame in the executable program or normal dynamic
+// shared objects then it will call all registered dynamic lookup functions
+// in registration order until either one of them returns true, or the end
+// of the list is reached. This lookup will happen before libunwind searches
+// any eh-frames registered via __register_frame or
+// __unw_add_dynamic_eh_frame_section.
+//
+// Returns UNW_ESUCCESS for successful registrations. If the given callback
+// has already been registered then UNW_EINVAL will be returned. If all
+// available callback entries are in use then UNW_ENOMEM will be returned.
+extern int __unw_add_find_dynamic_unwind_sections(
+ unw_find_dynamic_unwind_sections find_dynamic_unwind_sections);
+
+// Deregister a dynacim unwind-info lookup callback.
+//
+// Returns UNW_ESUCCESS for successful deregistrations. If the given callback
+// has already been registered then UNW_EINVAL will be returned.
+extern int __unw_remove_find_dynamic_unwind_sections(
+ unw_find_dynamic_unwind_sections find_dynamic_unwind_sections);
+
+#endif
+
#if defined(_LIBUNWIND_ARM_EHABI)
extern const uint32_t* decode_eht_entry(const uint32_t*, size_t*, size_t*);
extern _Unwind_Reason_Code _Unwind_VRS_Interpret(_Unwind_Context *context,