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