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