xref: /freebsd-src/contrib/llvm-project/libunwind/src/FrameHeaderCache.hpp (revision 5f757f3ff9144b609b3c433dfd370cc6bdc191ad)
15ffd83dbSDimitry Andric //===-FrameHeaderCache.hpp ------------------------------------------------===//
25ffd83dbSDimitry Andric //
35ffd83dbSDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
45ffd83dbSDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
55ffd83dbSDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
65ffd83dbSDimitry Andric //
75ffd83dbSDimitry Andric // Cache the elf program headers necessary to unwind the stack more efficiently
85ffd83dbSDimitry Andric // in the presence of many dsos.
95ffd83dbSDimitry Andric //
105ffd83dbSDimitry Andric //===----------------------------------------------------------------------===//
115ffd83dbSDimitry Andric 
125ffd83dbSDimitry Andric #ifndef __FRAMEHEADER_CACHE_HPP__
135ffd83dbSDimitry Andric #define __FRAMEHEADER_CACHE_HPP__
145ffd83dbSDimitry Andric 
155ffd83dbSDimitry Andric #include "config.h"
165ffd83dbSDimitry Andric #include <limits.h>
175ffd83dbSDimitry Andric 
185ffd83dbSDimitry Andric #ifdef _LIBUNWIND_DEBUG_FRAMEHEADER_CACHE
195ffd83dbSDimitry Andric #define _LIBUNWIND_FRAMEHEADERCACHE_TRACE0(x) _LIBUNWIND_LOG0(x)
205ffd83dbSDimitry Andric #define _LIBUNWIND_FRAMEHEADERCACHE_TRACE(msg, ...)                            \
215ffd83dbSDimitry Andric   _LIBUNWIND_LOG(msg, __VA_ARGS__)
225ffd83dbSDimitry Andric #else
235ffd83dbSDimitry Andric #define _LIBUNWIND_FRAMEHEADERCACHE_TRACE0(x)
245ffd83dbSDimitry Andric #define _LIBUNWIND_FRAMEHEADERCACHE_TRACE(msg, ...)
255ffd83dbSDimitry Andric #endif
265ffd83dbSDimitry Andric 
275ffd83dbSDimitry Andric // This cache should only be be used from within a dl_iterate_phdr callback.
285ffd83dbSDimitry Andric // dl_iterate_phdr does the necessary synchronization to prevent problems
295ffd83dbSDimitry Andric // with concurrent access via the libc load lock. Adding synchronization
305ffd83dbSDimitry Andric // for other uses is possible, but not currently done.
315ffd83dbSDimitry Andric 
325ffd83dbSDimitry Andric class _LIBUNWIND_HIDDEN FrameHeaderCache {
335ffd83dbSDimitry Andric   struct CacheEntry {
LowPCFrameHeaderCache::CacheEntry34*5f757f3fSDimitry Andric     uintptr_t LowPC() { return Info.dso_base; }
HighPCFrameHeaderCache::CacheEntry35*5f757f3fSDimitry Andric     uintptr_t HighPC() { return Info.dso_base + Info.text_segment_length; }
365ffd83dbSDimitry Andric     UnwindInfoSections Info;
375ffd83dbSDimitry Andric     CacheEntry *Next;
385ffd83dbSDimitry Andric   };
395ffd83dbSDimitry Andric 
405ffd83dbSDimitry Andric   static const size_t kCacheEntryCount = 8;
415ffd83dbSDimitry Andric 
425ffd83dbSDimitry Andric   // Can't depend on the C++ standard library in libunwind, so use an array to
435ffd83dbSDimitry Andric   // allocate the entries, and two linked lists for ordering unused and recently
44*5f757f3fSDimitry Andric   // used entries.  FIXME: Would the extra memory for a doubly-linked list
455ffd83dbSDimitry Andric   // be better than the runtime cost of traversing a very short singly-linked
465ffd83dbSDimitry Andric   // list on a cache miss? The entries themselves are all small and consecutive,
475ffd83dbSDimitry Andric   // so unlikely to cause page faults when following the pointers. The memory
485ffd83dbSDimitry Andric   // spent on additional pointers could also be spent on more entries.
495ffd83dbSDimitry Andric 
505ffd83dbSDimitry Andric   CacheEntry Entries[kCacheEntryCount];
515ffd83dbSDimitry Andric   CacheEntry *MostRecentlyUsed;
525ffd83dbSDimitry Andric   CacheEntry *Unused;
535ffd83dbSDimitry Andric 
resetCache()545ffd83dbSDimitry Andric   void resetCache() {
555ffd83dbSDimitry Andric     _LIBUNWIND_FRAMEHEADERCACHE_TRACE0("FrameHeaderCache reset");
565ffd83dbSDimitry Andric     MostRecentlyUsed = nullptr;
575ffd83dbSDimitry Andric     Unused = &Entries[0];
585ffd83dbSDimitry Andric     for (size_t i = 0; i < kCacheEntryCount - 1; i++) {
595ffd83dbSDimitry Andric       Entries[i].Next = &Entries[i + 1];
605ffd83dbSDimitry Andric     }
615ffd83dbSDimitry Andric     Entries[kCacheEntryCount - 1].Next = nullptr;
625ffd83dbSDimitry Andric   }
635ffd83dbSDimitry Andric 
cacheNeedsReset(dl_phdr_info * PInfo)645ffd83dbSDimitry Andric   bool cacheNeedsReset(dl_phdr_info *PInfo) {
655ffd83dbSDimitry Andric     // C libraries increment dl_phdr_info.adds and dl_phdr_info.subs when
665ffd83dbSDimitry Andric     // loading and unloading shared libraries. If these values change between
675ffd83dbSDimitry Andric     // iterations of dl_iterate_phdr, then invalidate the cache.
685ffd83dbSDimitry Andric 
695ffd83dbSDimitry Andric     // These are static to avoid needing an initializer, and unsigned long long
705ffd83dbSDimitry Andric     // because that is their type within the extended dl_phdr_info.  Initialize
715ffd83dbSDimitry Andric     // these to something extremely unlikely to be found upon the first call to
725ffd83dbSDimitry Andric     // dl_iterate_phdr.
735ffd83dbSDimitry Andric     static unsigned long long LastAdds = ULLONG_MAX;
745ffd83dbSDimitry Andric     static unsigned long long LastSubs = ULLONG_MAX;
755ffd83dbSDimitry Andric     if (PInfo->dlpi_adds != LastAdds || PInfo->dlpi_subs != LastSubs) {
765ffd83dbSDimitry Andric       // Resetting the entire cache is a big hammer, but this path is rare--
775ffd83dbSDimitry Andric       // usually just on the very first call, when the cache is empty anyway--so
785ffd83dbSDimitry Andric       // added complexity doesn't buy much.
795ffd83dbSDimitry Andric       LastAdds = PInfo->dlpi_adds;
805ffd83dbSDimitry Andric       LastSubs = PInfo->dlpi_subs;
815ffd83dbSDimitry Andric       resetCache();
825ffd83dbSDimitry Andric       return true;
835ffd83dbSDimitry Andric     }
845ffd83dbSDimitry Andric     return false;
855ffd83dbSDimitry Andric   }
865ffd83dbSDimitry Andric 
875ffd83dbSDimitry Andric public:
find(dl_phdr_info * PInfo,size_t,void * data)885ffd83dbSDimitry Andric   bool find(dl_phdr_info *PInfo, size_t, void *data) {
895ffd83dbSDimitry Andric     if (cacheNeedsReset(PInfo) || MostRecentlyUsed == nullptr)
905ffd83dbSDimitry Andric       return false;
915ffd83dbSDimitry Andric 
925ffd83dbSDimitry Andric     auto *CBData = static_cast<dl_iterate_cb_data *>(data);
935ffd83dbSDimitry Andric     CacheEntry *Current = MostRecentlyUsed;
945ffd83dbSDimitry Andric     CacheEntry *Previous = nullptr;
955ffd83dbSDimitry Andric     while (Current != nullptr) {
965ffd83dbSDimitry Andric       _LIBUNWIND_FRAMEHEADERCACHE_TRACE(
975ffd83dbSDimitry Andric           "FrameHeaderCache check %lx in [%lx - %lx)", CBData->targetAddr,
985ffd83dbSDimitry Andric           Current->LowPC(), Current->HighPC());
995ffd83dbSDimitry Andric       if (Current->LowPC() <= CBData->targetAddr &&
1005ffd83dbSDimitry Andric           CBData->targetAddr < Current->HighPC()) {
1015ffd83dbSDimitry Andric         _LIBUNWIND_FRAMEHEADERCACHE_TRACE(
1025ffd83dbSDimitry Andric             "FrameHeaderCache hit %lx in [%lx - %lx)", CBData->targetAddr,
1035ffd83dbSDimitry Andric             Current->LowPC(), Current->HighPC());
1045ffd83dbSDimitry Andric         if (Previous) {
1055ffd83dbSDimitry Andric           // If there is no Previous, then Current is already the
1065ffd83dbSDimitry Andric           // MostRecentlyUsed, and no need to move it up.
1075ffd83dbSDimitry Andric           Previous->Next = Current->Next;
1085ffd83dbSDimitry Andric           Current->Next = MostRecentlyUsed;
1095ffd83dbSDimitry Andric           MostRecentlyUsed = Current;
1105ffd83dbSDimitry Andric         }
1115ffd83dbSDimitry Andric         *CBData->sects = Current->Info;
1125ffd83dbSDimitry Andric         return true;
1135ffd83dbSDimitry Andric       }
1145ffd83dbSDimitry Andric       Previous = Current;
1155ffd83dbSDimitry Andric       Current = Current->Next;
1165ffd83dbSDimitry Andric     }
1175ffd83dbSDimitry Andric     _LIBUNWIND_FRAMEHEADERCACHE_TRACE("FrameHeaderCache miss for address %lx",
1185ffd83dbSDimitry Andric                                       CBData->targetAddr);
1195ffd83dbSDimitry Andric     return false;
1205ffd83dbSDimitry Andric   }
1215ffd83dbSDimitry Andric 
add(const UnwindInfoSections * UIS)1225ffd83dbSDimitry Andric   void add(const UnwindInfoSections *UIS) {
1235ffd83dbSDimitry Andric     CacheEntry *Current = nullptr;
1245ffd83dbSDimitry Andric 
1255ffd83dbSDimitry Andric     if (Unused != nullptr) {
1265ffd83dbSDimitry Andric       Current = Unused;
1275ffd83dbSDimitry Andric       Unused = Unused->Next;
1285ffd83dbSDimitry Andric     } else {
1295ffd83dbSDimitry Andric       Current = MostRecentlyUsed;
1305ffd83dbSDimitry Andric       CacheEntry *Previous = nullptr;
1315ffd83dbSDimitry Andric       while (Current->Next != nullptr) {
1325ffd83dbSDimitry Andric         Previous = Current;
1335ffd83dbSDimitry Andric         Current = Current->Next;
1345ffd83dbSDimitry Andric       }
1355ffd83dbSDimitry Andric       Previous->Next = nullptr;
1365ffd83dbSDimitry Andric       _LIBUNWIND_FRAMEHEADERCACHE_TRACE("FrameHeaderCache evict [%lx - %lx)",
1375ffd83dbSDimitry Andric                                         Current->LowPC(), Current->HighPC());
1385ffd83dbSDimitry Andric     }
1395ffd83dbSDimitry Andric 
1405ffd83dbSDimitry Andric     Current->Info = *UIS;
1415ffd83dbSDimitry Andric     Current->Next = MostRecentlyUsed;
1425ffd83dbSDimitry Andric     MostRecentlyUsed = Current;
1435ffd83dbSDimitry Andric     _LIBUNWIND_FRAMEHEADERCACHE_TRACE("FrameHeaderCache add [%lx - %lx)",
1445ffd83dbSDimitry Andric                                       MostRecentlyUsed->LowPC(),
1455ffd83dbSDimitry Andric                                       MostRecentlyUsed->HighPC());
1465ffd83dbSDimitry Andric   }
1475ffd83dbSDimitry Andric };
1485ffd83dbSDimitry Andric 
1495ffd83dbSDimitry Andric #endif // __FRAMEHEADER_CACHE_HPP__
150