[libunwind] 0751fc6 - [libunwind] On Darwin, add a callback-based lookup scheme for JIT'd unwind info.

Lang Hames via cfe-commits cfe-commits at lists.llvm.org
Fri Feb 10 14:37:03 PST 2023


Author: Lang Hames
Date: 2023-02-10T14:36:25-08:00
New Revision: 0751fc68b976d25dd3041217dad16622cf151cd6

URL: https://github.com/llvm/llvm-project/commit/0751fc68b976d25dd3041217dad16622cf151cd6
DIFF: https://github.com/llvm/llvm-project/commit/0751fc68b976d25dd3041217dad16622cf151cd6.diff

LOG: [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

Added: 
    

Modified: 
    libunwind/src/AddressSpace.hpp
    libunwind/src/libunwind.cpp
    libunwind/src/libunwind_ext.h

Removed: 
    


################################################################################
diff  --git a/libunwind/src/AddressSpace.hpp b/libunwind/src/AddressSpace.hpp
index 26d289068b38c..1abbc82254687 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 0faea2b78570f..1bd18659b7860 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 fdc533cb89622..28db43a4f6eef 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,


        


More information about the cfe-commits mailing list