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