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