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