1 //===-FrameHeaderCache.hpp ------------------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 // Cache the elf program headers necessary to unwind the stack more efficiently 8 // in the presence of many dsos. 9 // 10 //===----------------------------------------------------------------------===// 11 12 #ifndef __FRAMEHEADER_CACHE_HPP__ 13 #define __FRAMEHEADER_CACHE_HPP__ 14 15 #include "config.h" 16 #include <limits.h> 17 18 #ifdef _LIBUNWIND_DEBUG_FRAMEHEADER_CACHE 19 #define _LIBUNWIND_FRAMEHEADERCACHE_TRACE0(x) _LIBUNWIND_LOG0(x) 20 #define _LIBUNWIND_FRAMEHEADERCACHE_TRACE(msg, ...) \ 21 _LIBUNWIND_LOG(msg, __VA_ARGS__) 22 #else 23 #define _LIBUNWIND_FRAMEHEADERCACHE_TRACE0(x) 24 #define _LIBUNWIND_FRAMEHEADERCACHE_TRACE(msg, ...) 25 #endif 26 27 // This cache should only be be used from within a dl_iterate_phdr callback. 28 // dl_iterate_phdr does the necessary synchronization to prevent problems 29 // with concurrent access via the libc load lock. Adding synchronization 30 // for other uses is possible, but not currently done. 31 32 class _LIBUNWIND_HIDDEN FrameHeaderCache { 33 struct CacheEntry { LowPCFrameHeaderCache::CacheEntry34 uintptr_t LowPC() { return Info.dso_base; }; HighPCFrameHeaderCache::CacheEntry35 uintptr_t HighPC() { return Info.dso_base + Info.text_segment_length; }; 36 UnwindInfoSections Info; 37 CacheEntry *Next; 38 }; 39 40 static const size_t kCacheEntryCount = 8; 41 42 // Can't depend on the C++ standard library in libunwind, so use an array to 43 // allocate the entries, and two linked lists for ordering unused and recently 44 // used entries. FIXME: Would the the extra memory for a doubly-linked list 45 // be better than the runtime cost of traversing a very short singly-linked 46 // list on a cache miss? The entries themselves are all small and consecutive, 47 // so unlikely to cause page faults when following the pointers. The memory 48 // spent on additional pointers could also be spent on more entries. 49 50 CacheEntry Entries[kCacheEntryCount]; 51 CacheEntry *MostRecentlyUsed; 52 CacheEntry *Unused; 53 resetCache()54 void resetCache() { 55 _LIBUNWIND_FRAMEHEADERCACHE_TRACE0("FrameHeaderCache reset"); 56 MostRecentlyUsed = nullptr; 57 Unused = &Entries[0]; 58 for (size_t i = 0; i < kCacheEntryCount - 1; i++) { 59 Entries[i].Next = &Entries[i + 1]; 60 } 61 Entries[kCacheEntryCount - 1].Next = nullptr; 62 } 63 cacheNeedsReset(dl_phdr_info * PInfo)64 bool cacheNeedsReset(dl_phdr_info *PInfo) { 65 // C libraries increment dl_phdr_info.adds and dl_phdr_info.subs when 66 // loading and unloading shared libraries. If these values change between 67 // iterations of dl_iterate_phdr, then invalidate the cache. 68 69 // These are static to avoid needing an initializer, and unsigned long long 70 // because that is their type within the extended dl_phdr_info. Initialize 71 // these to something extremely unlikely to be found upon the first call to 72 // dl_iterate_phdr. 73 static unsigned long long LastAdds = ULLONG_MAX; 74 static unsigned long long LastSubs = ULLONG_MAX; 75 if (PInfo->dlpi_adds != LastAdds || PInfo->dlpi_subs != LastSubs) { 76 // Resetting the entire cache is a big hammer, but this path is rare-- 77 // usually just on the very first call, when the cache is empty anyway--so 78 // added complexity doesn't buy much. 79 LastAdds = PInfo->dlpi_adds; 80 LastSubs = PInfo->dlpi_subs; 81 resetCache(); 82 return true; 83 } 84 return false; 85 } 86 87 public: find(dl_phdr_info * PInfo,size_t,void * data)88 bool find(dl_phdr_info *PInfo, size_t, void *data) { 89 if (cacheNeedsReset(PInfo) || MostRecentlyUsed == nullptr) 90 return false; 91 92 auto *CBData = static_cast<dl_iterate_cb_data *>(data); 93 CacheEntry *Current = MostRecentlyUsed; 94 CacheEntry *Previous = nullptr; 95 while (Current != nullptr) { 96 _LIBUNWIND_FRAMEHEADERCACHE_TRACE( 97 "FrameHeaderCache check %lx in [%lx - %lx)", CBData->targetAddr, 98 Current->LowPC(), Current->HighPC()); 99 if (Current->LowPC() <= CBData->targetAddr && 100 CBData->targetAddr < Current->HighPC()) { 101 _LIBUNWIND_FRAMEHEADERCACHE_TRACE( 102 "FrameHeaderCache hit %lx in [%lx - %lx)", CBData->targetAddr, 103 Current->LowPC(), Current->HighPC()); 104 if (Previous) { 105 // If there is no Previous, then Current is already the 106 // MostRecentlyUsed, and no need to move it up. 107 Previous->Next = Current->Next; 108 Current->Next = MostRecentlyUsed; 109 MostRecentlyUsed = Current; 110 } 111 *CBData->sects = Current->Info; 112 return true; 113 } 114 Previous = Current; 115 Current = Current->Next; 116 } 117 _LIBUNWIND_FRAMEHEADERCACHE_TRACE("FrameHeaderCache miss for address %lx", 118 CBData->targetAddr); 119 return false; 120 } 121 add(const UnwindInfoSections * UIS)122 void add(const UnwindInfoSections *UIS) { 123 CacheEntry *Current = nullptr; 124 125 if (Unused != nullptr) { 126 Current = Unused; 127 Unused = Unused->Next; 128 } else { 129 Current = MostRecentlyUsed; 130 CacheEntry *Previous = nullptr; 131 while (Current->Next != nullptr) { 132 Previous = Current; 133 Current = Current->Next; 134 } 135 Previous->Next = nullptr; 136 _LIBUNWIND_FRAMEHEADERCACHE_TRACE("FrameHeaderCache evict [%lx - %lx)", 137 Current->LowPC(), Current->HighPC()); 138 } 139 140 Current->Info = *UIS; 141 Current->Next = MostRecentlyUsed; 142 MostRecentlyUsed = Current; 143 _LIBUNWIND_FRAMEHEADERCACHE_TRACE("FrameHeaderCache add [%lx - %lx)", 144 MostRecentlyUsed->LowPC(), 145 MostRecentlyUsed->HighPC()); 146 } 147 }; 148 149 #endif // __FRAMEHEADER_CACHE_HPP__ 150