//===-FrameHeaderCache.hpp ------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // Cache the elf program headers necessary to unwind the stack more efficiently // in the presence of many dsos. // //===----------------------------------------------------------------------===// #ifndef __FRAMEHEADER_CACHE_HPP__ #define __FRAMEHEADER_CACHE_HPP__ #include "config.h" #include #ifdef _LIBUNWIND_DEBUG_FRAMEHEADER_CACHE #define _LIBUNWIND_FRAMEHEADERCACHE_TRACE0(x) _LIBUNWIND_LOG0(x) #define _LIBUNWIND_FRAMEHEADERCACHE_TRACE(msg, ...) \ _LIBUNWIND_LOG(msg, __VA_ARGS__) #else #define _LIBUNWIND_FRAMEHEADERCACHE_TRACE0(x) #define _LIBUNWIND_FRAMEHEADERCACHE_TRACE(msg, ...) #endif // This cache should only be be used from within a dl_iterate_phdr callback. // dl_iterate_phdr does the necessary synchronization to prevent problems // with concurrent access via the libc load lock. Adding synchronization // for other uses is possible, but not currently done. class _LIBUNWIND_HIDDEN FrameHeaderCache { struct CacheEntry { uintptr_t LowPC() { return Info.dso_base; }; uintptr_t HighPC() { return Info.dso_base + Info.dwarf_section_length; }; UnwindInfoSections Info; CacheEntry *Next; }; static const size_t kCacheEntryCount = 8; // Can't depend on the C++ standard library in libunwind, so use an array to // allocate the entries, and two linked lists for ordering unused and recently // used entries. FIXME: Would the the extra memory for a doubly-linked list // be better than the runtime cost of traversing a very short singly-linked // list on a cache miss? The entries themselves are all small and consecutive, // so unlikely to cause page faults when following the pointers. The memory // spent on additional pointers could also be spent on more entries. CacheEntry Entries[kCacheEntryCount]; CacheEntry *MostRecentlyUsed; CacheEntry *Unused; void resetCache() { _LIBUNWIND_FRAMEHEADERCACHE_TRACE0("FrameHeaderCache reset"); MostRecentlyUsed = nullptr; Unused = &Entries[0]; for (size_t i = 0; i < kCacheEntryCount - 1; i++) { Entries[i].Next = &Entries[i + 1]; } Entries[kCacheEntryCount - 1].Next = nullptr; } bool cacheNeedsReset(dl_phdr_info *PInfo) { // C libraries increment dl_phdr_info.adds and dl_phdr_info.subs when // loading and unloading shared libraries. If these values change between // iterations of dl_iterate_phdr, then invalidate the cache. // These are static to avoid needing an initializer, and unsigned long long // because that is their type within the extended dl_phdr_info. Initialize // these to something extremely unlikely to be found upon the first call to // dl_iterate_phdr. static unsigned long long LastAdds = ULLONG_MAX; static unsigned long long LastSubs = ULLONG_MAX; if (PInfo->dlpi_adds != LastAdds || PInfo->dlpi_subs != LastSubs) { // Resetting the entire cache is a big hammer, but this path is rare-- // usually just on the very first call, when the cache is empty anyway--so // added complexity doesn't buy much. LastAdds = PInfo->dlpi_adds; LastSubs = PInfo->dlpi_subs; resetCache(); return true; } return false; } public: bool find(dl_phdr_info *PInfo, size_t, void *data) { if (cacheNeedsReset(PInfo) || MostRecentlyUsed == nullptr) return false; auto *CBData = static_cast(data); CacheEntry *Current = MostRecentlyUsed; CacheEntry *Previous = nullptr; while (Current != nullptr) { _LIBUNWIND_FRAMEHEADERCACHE_TRACE( "FrameHeaderCache check %lx in [%lx - %lx)", CBData->targetAddr, Current->LowPC(), Current->HighPC()); if (Current->LowPC() <= CBData->targetAddr && CBData->targetAddr < Current->HighPC()) { _LIBUNWIND_FRAMEHEADERCACHE_TRACE( "FrameHeaderCache hit %lx in [%lx - %lx)", CBData->targetAddr, Current->LowPC(), Current->HighPC()); if (Previous) { // If there is no Previous, then Current is already the // MostRecentlyUsed, and no need to move it up. Previous->Next = Current->Next; Current->Next = MostRecentlyUsed; MostRecentlyUsed = Current; } *CBData->sects = Current->Info; return true; } Previous = Current; Current = Current->Next; } _LIBUNWIND_FRAMEHEADERCACHE_TRACE("FrameHeaderCache miss for address %lx", CBData->targetAddr); return false; } void add(const UnwindInfoSections *UIS) { CacheEntry *Current = nullptr; if (Unused != nullptr) { Current = Unused; Unused = Unused->Next; } else { Current = MostRecentlyUsed; CacheEntry *Previous = nullptr; while (Current->Next != nullptr) { Previous = Current; Current = Current->Next; } Previous->Next = nullptr; _LIBUNWIND_FRAMEHEADERCACHE_TRACE("FrameHeaderCache evict [%lx - %lx)", Current->LowPC(), Current->HighPC()); } Current->Info = *UIS; Current->Next = MostRecentlyUsed; MostRecentlyUsed = Current; _LIBUNWIND_FRAMEHEADERCACHE_TRACE("FrameHeaderCache add [%lx - %lx)", MostRecentlyUsed->LowPC(), MostRecentlyUsed->HighPC()); } }; #endif // __FRAMEHEADER_CACHE_HPP__