xref: /openbsd-src/gnu/llvm/libunwind/src/FrameHeaderCache.hpp (revision b3056a3bf3ec413ee3116a1cefa486c0a9d2e580)
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