1 //===-FrameHeaderCache.hpp ------------------------------------------------===//
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
7 // Cache the elf program headers necessary to unwind the stack more efficiently
8 // in the presence of many dsos.
10 //===----------------------------------------------------------------------===//
12 #ifndef __FRAMEHEADER_CACHE_HPP__
13 #define __FRAMEHEADER_CACHE_HPP__
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__)
23 #define _LIBUNWIND_FRAMEHEADERCACHE_TRACE0(x)
24 #define _LIBUNWIND_FRAMEHEADERCACHE_TRACE(msg, ...)
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.
32 class _LIBUNWIND_HIDDEN FrameHeaderCache
{
34 uintptr_t LowPC() { return Info
.dso_base
; }
35 uintptr_t HighPC() { return Info
.dso_base
+ Info
.text_segment_length
; }
36 UnwindInfoSections Info
;
40 static const size_t kCacheEntryCount
= 8;
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 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.
50 CacheEntry Entries
[kCacheEntryCount
];
51 CacheEntry
*MostRecentlyUsed
;
55 _LIBUNWIND_FRAMEHEADERCACHE_TRACE0("FrameHeaderCache reset");
56 MostRecentlyUsed
= nullptr;
58 for (size_t i
= 0; i
< kCacheEntryCount
- 1; i
++) {
59 Entries
[i
].Next
= &Entries
[i
+ 1];
61 Entries
[kCacheEntryCount
- 1].Next
= nullptr;
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.
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
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
;
88 bool find(dl_phdr_info
*PInfo
, size_t, void *data
) {
89 if (cacheNeedsReset(PInfo
) || MostRecentlyUsed
== nullptr)
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());
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
;
111 *CBData
->sects
= Current
->Info
;
115 Current
= Current
->Next
;
117 _LIBUNWIND_FRAMEHEADERCACHE_TRACE("FrameHeaderCache miss for address %lx",
122 void add(const UnwindInfoSections
*UIS
) {
123 CacheEntry
*Current
= nullptr;
125 if (Unused
!= nullptr) {
127 Unused
= Unused
->Next
;
129 Current
= MostRecentlyUsed
;
130 CacheEntry
*Previous
= nullptr;
131 while (Current
->Next
!= nullptr) {
133 Current
= Current
->Next
;
135 Previous
->Next
= nullptr;
136 _LIBUNWIND_FRAMEHEADERCACHE_TRACE("FrameHeaderCache evict [%lx - %lx)",
137 Current
->LowPC(), Current
->HighPC());
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());
149 #endif // __FRAMEHEADER_CACHE_HPP__