mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 11:37:35 +00:00
b09096691a
Added libunwind from LLVM 17.0.6. The library includes functions required for C++ exception handling.
149 lines
5.6 KiB
C++
149 lines
5.6 KiB
C++
//===-FrameHeaderCache.hpp ------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
// Cache the elf program headers necessary to unwind the stack more efficiently
|
|
// in the presence of many dsos.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef __FRAMEHEADER_CACHE_HPP__
|
|
#define __FRAMEHEADER_CACHE_HPP__
|
|
|
|
#include "third_party/libunwind/config.h"
|
|
#include "libc/isystem/limits.h"
|
|
|
|
#ifdef _LIBUNWIND_DEBUG_FRAMEHEADER_CACHE
|
|
#define _LIBUNWIND_FRAMEHEADERCACHE_TRACE0(x) _LIBUNWIND_LOG0(x)
|
|
#define _LIBUNWIND_FRAMEHEADERCACHE_TRACE(msg, ...) \
|
|
_LIBUNWIND_LOG(msg, __VA_ARGS__)
|
|
#else
|
|
#define _LIBUNWIND_FRAMEHEADERCACHE_TRACE0(x)
|
|
#define _LIBUNWIND_FRAMEHEADERCACHE_TRACE(msg, ...)
|
|
#endif
|
|
|
|
// This cache should only be be used from within a dl_iterate_phdr callback.
|
|
// dl_iterate_phdr does the necessary synchronization to prevent problems
|
|
// with concurrent access via the libc load lock. Adding synchronization
|
|
// for other uses is possible, but not currently done.
|
|
|
|
class _LIBUNWIND_HIDDEN FrameHeaderCache {
|
|
struct CacheEntry {
|
|
uintptr_t LowPC() { return Info.dso_base; };
|
|
uintptr_t HighPC() { return Info.dso_base + Info.text_segment_length; };
|
|
UnwindInfoSections Info;
|
|
CacheEntry *Next;
|
|
};
|
|
|
|
static const size_t kCacheEntryCount = 8;
|
|
|
|
// Can't depend on the C++ standard library in libunwind, so use an array to
|
|
// allocate the entries, and two linked lists for ordering unused and recently
|
|
// used entries. FIXME: Would the the extra memory for a doubly-linked list
|
|
// be better than the runtime cost of traversing a very short singly-linked
|
|
// list on a cache miss? The entries themselves are all small and consecutive,
|
|
// so unlikely to cause page faults when following the pointers. The memory
|
|
// spent on additional pointers could also be spent on more entries.
|
|
|
|
CacheEntry Entries[kCacheEntryCount];
|
|
CacheEntry *MostRecentlyUsed;
|
|
CacheEntry *Unused;
|
|
|
|
void resetCache() {
|
|
_LIBUNWIND_FRAMEHEADERCACHE_TRACE0("FrameHeaderCache reset");
|
|
MostRecentlyUsed = nullptr;
|
|
Unused = &Entries[0];
|
|
for (size_t i = 0; i < kCacheEntryCount - 1; i++) {
|
|
Entries[i].Next = &Entries[i + 1];
|
|
}
|
|
Entries[kCacheEntryCount - 1].Next = nullptr;
|
|
}
|
|
|
|
bool cacheNeedsReset(dl_phdr_info *PInfo) {
|
|
// C libraries increment dl_phdr_info.adds and dl_phdr_info.subs when
|
|
// loading and unloading shared libraries. If these values change between
|
|
// iterations of dl_iterate_phdr, then invalidate the cache.
|
|
|
|
// These are static to avoid needing an initializer, and unsigned long long
|
|
// because that is their type within the extended dl_phdr_info. Initialize
|
|
// these to something extremely unlikely to be found upon the first call to
|
|
// dl_iterate_phdr.
|
|
static unsigned long long LastAdds = ULLONG_MAX;
|
|
static unsigned long long LastSubs = ULLONG_MAX;
|
|
if (PInfo->dlpi_adds != LastAdds || PInfo->dlpi_subs != LastSubs) {
|
|
// Resetting the entire cache is a big hammer, but this path is rare--
|
|
// usually just on the very first call, when the cache is empty anyway--so
|
|
// added complexity doesn't buy much.
|
|
LastAdds = PInfo->dlpi_adds;
|
|
LastSubs = PInfo->dlpi_subs;
|
|
resetCache();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public:
|
|
bool find(dl_phdr_info *PInfo, size_t, void *data) {
|
|
if (cacheNeedsReset(PInfo) || MostRecentlyUsed == nullptr)
|
|
return false;
|
|
|
|
auto *CBData = static_cast<dl_iterate_cb_data *>(data);
|
|
CacheEntry *Current = MostRecentlyUsed;
|
|
CacheEntry *Previous = nullptr;
|
|
while (Current != nullptr) {
|
|
_LIBUNWIND_FRAMEHEADERCACHE_TRACE(
|
|
"FrameHeaderCache check %lx in [%lx - %lx)", CBData->targetAddr,
|
|
Current->LowPC(), Current->HighPC());
|
|
if (Current->LowPC() <= CBData->targetAddr &&
|
|
CBData->targetAddr < Current->HighPC()) {
|
|
_LIBUNWIND_FRAMEHEADERCACHE_TRACE(
|
|
"FrameHeaderCache hit %lx in [%lx - %lx)", CBData->targetAddr,
|
|
Current->LowPC(), Current->HighPC());
|
|
if (Previous) {
|
|
// If there is no Previous, then Current is already the
|
|
// MostRecentlyUsed, and no need to move it up.
|
|
Previous->Next = Current->Next;
|
|
Current->Next = MostRecentlyUsed;
|
|
MostRecentlyUsed = Current;
|
|
}
|
|
*CBData->sects = Current->Info;
|
|
return true;
|
|
}
|
|
Previous = Current;
|
|
Current = Current->Next;
|
|
}
|
|
_LIBUNWIND_FRAMEHEADERCACHE_TRACE("FrameHeaderCache miss for address %lx",
|
|
CBData->targetAddr);
|
|
return false;
|
|
}
|
|
|
|
void add(const UnwindInfoSections *UIS) {
|
|
CacheEntry *Current = nullptr;
|
|
|
|
if (Unused != nullptr) {
|
|
Current = Unused;
|
|
Unused = Unused->Next;
|
|
} else {
|
|
Current = MostRecentlyUsed;
|
|
CacheEntry *Previous = nullptr;
|
|
while (Current->Next != nullptr) {
|
|
Previous = Current;
|
|
Current = Current->Next;
|
|
}
|
|
Previous->Next = nullptr;
|
|
_LIBUNWIND_FRAMEHEADERCACHE_TRACE("FrameHeaderCache evict [%lx - %lx)",
|
|
Current->LowPC(), Current->HighPC());
|
|
}
|
|
|
|
Current->Info = *UIS;
|
|
Current->Next = MostRecentlyUsed;
|
|
MostRecentlyUsed = Current;
|
|
_LIBUNWIND_FRAMEHEADERCACHE_TRACE("FrameHeaderCache add [%lx - %lx)",
|
|
MostRecentlyUsed->LowPC(),
|
|
MostRecentlyUsed->HighPC());
|
|
}
|
|
};
|
|
|
|
#endif // __FRAMEHEADER_CACHE_HPP__
|