xref: /freebsd/contrib/llvm-project/compiler-rt/lib/scudo/standalone/stack_depot.h (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
15ffd83dbSDimitry Andric //===-- stack_depot.h -------------------------------------------*- C++ -*-===//
25ffd83dbSDimitry Andric //
35ffd83dbSDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
45ffd83dbSDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
55ffd83dbSDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
65ffd83dbSDimitry Andric //
75ffd83dbSDimitry Andric //===----------------------------------------------------------------------===//
85ffd83dbSDimitry Andric 
95ffd83dbSDimitry Andric #ifndef SCUDO_STACK_DEPOT_H_
105ffd83dbSDimitry Andric #define SCUDO_STACK_DEPOT_H_
115ffd83dbSDimitry Andric 
125ffd83dbSDimitry Andric #include "atomic_helpers.h"
13*0fca6ea1SDimitry Andric #include "common.h"
145ffd83dbSDimitry Andric #include "mutex.h"
155ffd83dbSDimitry Andric 
165ffd83dbSDimitry Andric namespace scudo {
175ffd83dbSDimitry Andric 
185ffd83dbSDimitry Andric class MurMur2HashBuilder {
195ffd83dbSDimitry Andric   static const u32 M = 0x5bd1e995;
205ffd83dbSDimitry Andric   static const u32 Seed = 0x9747b28c;
215ffd83dbSDimitry Andric   static const u32 R = 24;
225ffd83dbSDimitry Andric   u32 H;
235ffd83dbSDimitry Andric 
245ffd83dbSDimitry Andric public:
255ffd83dbSDimitry Andric   explicit MurMur2HashBuilder(u32 Init = 0) { H = Seed ^ Init; }
add(u32 K)265ffd83dbSDimitry Andric   void add(u32 K) {
275ffd83dbSDimitry Andric     K *= M;
285ffd83dbSDimitry Andric     K ^= K >> R;
295ffd83dbSDimitry Andric     K *= M;
305ffd83dbSDimitry Andric     H *= M;
315ffd83dbSDimitry Andric     H ^= K;
325ffd83dbSDimitry Andric   }
get()335ffd83dbSDimitry Andric   u32 get() {
345ffd83dbSDimitry Andric     u32 X = H;
355ffd83dbSDimitry Andric     X ^= X >> 13;
365ffd83dbSDimitry Andric     X *= M;
375ffd83dbSDimitry Andric     X ^= X >> 15;
385ffd83dbSDimitry Andric     return X;
395ffd83dbSDimitry Andric   }
405ffd83dbSDimitry Andric };
415ffd83dbSDimitry Andric 
42*0fca6ea1SDimitry Andric class alignas(atomic_u64) StackDepot {
435ffd83dbSDimitry Andric   HybridMutex RingEndMu;
44fe6060f1SDimitry Andric   u32 RingEnd = 0;
455ffd83dbSDimitry Andric 
465ffd83dbSDimitry Andric   // This data structure stores a stack trace for each allocation and
475ffd83dbSDimitry Andric   // deallocation when stack trace recording is enabled, that may be looked up
485ffd83dbSDimitry Andric   // using a hash of the stack trace. The lower bits of the hash are an index
495ffd83dbSDimitry Andric   // into the Tab array, which stores an index into the Ring array where the
505ffd83dbSDimitry Andric   // stack traces are stored. As the name implies, Ring is a ring buffer, so a
515ffd83dbSDimitry Andric   // stack trace may wrap around to the start of the array.
525ffd83dbSDimitry Andric   //
535ffd83dbSDimitry Andric   // Each stack trace in Ring is prefixed by a stack trace marker consisting of
545ffd83dbSDimitry Andric   // a fixed 1 bit in bit 0 (this allows disambiguation between stack frames
555ffd83dbSDimitry Andric   // and stack trace markers in the case where instruction pointers are 4-byte
565ffd83dbSDimitry Andric   // aligned, as they are on arm64), the stack trace hash in bits 1-32, and the
575ffd83dbSDimitry Andric   // size of the stack trace in bits 33-63.
585ffd83dbSDimitry Andric   //
595ffd83dbSDimitry Andric   // The insert() function is potentially racy in its accesses to the Tab and
605ffd83dbSDimitry Andric   // Ring arrays, but find() is resilient to races in the sense that, barring
615ffd83dbSDimitry Andric   // hash collisions, it will either return the correct stack trace or no stack
625ffd83dbSDimitry Andric   // trace at all, even if two instances of insert() raced with one another.
635ffd83dbSDimitry Andric   // This is achieved by re-checking the hash of the stack trace before
645ffd83dbSDimitry Andric   // returning the trace.
655ffd83dbSDimitry Andric 
66*0fca6ea1SDimitry Andric   u32 RingSize = 0;
67*0fca6ea1SDimitry Andric   u32 RingMask = 0;
68*0fca6ea1SDimitry Andric   u32 TabMask = 0;
69*0fca6ea1SDimitry Andric   // This is immediately followed by RingSize atomic_u64 and
70*0fca6ea1SDimitry Andric   // (TabMask + 1) atomic_u32.
715ffd83dbSDimitry Andric 
getRing()72*0fca6ea1SDimitry Andric   atomic_u64 *getRing() {
73*0fca6ea1SDimitry Andric     return reinterpret_cast<atomic_u64 *>(reinterpret_cast<char *>(this) +
74*0fca6ea1SDimitry Andric                                           sizeof(StackDepot));
75*0fca6ea1SDimitry Andric   }
76*0fca6ea1SDimitry Andric 
getTab()77*0fca6ea1SDimitry Andric   atomic_u32 *getTab() {
78*0fca6ea1SDimitry Andric     return reinterpret_cast<atomic_u32 *>(reinterpret_cast<char *>(this) +
79*0fca6ea1SDimitry Andric                                           sizeof(StackDepot) +
80*0fca6ea1SDimitry Andric                                           sizeof(atomic_u64) * RingSize);
81*0fca6ea1SDimitry Andric   }
82*0fca6ea1SDimitry Andric 
getRing()83*0fca6ea1SDimitry Andric   const atomic_u64 *getRing() const {
84*0fca6ea1SDimitry Andric     return reinterpret_cast<const atomic_u64 *>(
85*0fca6ea1SDimitry Andric         reinterpret_cast<const char *>(this) + sizeof(StackDepot));
86*0fca6ea1SDimitry Andric   }
87*0fca6ea1SDimitry Andric 
getTab()88*0fca6ea1SDimitry Andric   const atomic_u32 *getTab() const {
89*0fca6ea1SDimitry Andric     return reinterpret_cast<const atomic_u32 *>(
90*0fca6ea1SDimitry Andric         reinterpret_cast<const char *>(this) + sizeof(StackDepot) +
91*0fca6ea1SDimitry Andric         sizeof(atomic_u64) * RingSize);
92*0fca6ea1SDimitry Andric   }
935ffd83dbSDimitry Andric 
945ffd83dbSDimitry Andric public:
init(u32 RingSz,u32 TabSz)95*0fca6ea1SDimitry Andric   void init(u32 RingSz, u32 TabSz) {
96*0fca6ea1SDimitry Andric     DCHECK(isPowerOfTwo(RingSz));
97*0fca6ea1SDimitry Andric     DCHECK(isPowerOfTwo(TabSz));
98*0fca6ea1SDimitry Andric     RingSize = RingSz;
99*0fca6ea1SDimitry Andric     RingMask = RingSz - 1;
100*0fca6ea1SDimitry Andric     TabMask = TabSz - 1;
101*0fca6ea1SDimitry Andric   }
102*0fca6ea1SDimitry Andric 
103*0fca6ea1SDimitry Andric   // Ensure that RingSize, RingMask and TabMask are set up in a way that
104*0fca6ea1SDimitry Andric   // all accesses are within range of BufSize.
isValid(uptr BufSize)105*0fca6ea1SDimitry Andric   bool isValid(uptr BufSize) const {
106*0fca6ea1SDimitry Andric     if (!isPowerOfTwo(RingSize))
107*0fca6ea1SDimitry Andric       return false;
108*0fca6ea1SDimitry Andric     uptr RingBytes = sizeof(atomic_u64) * RingSize;
109*0fca6ea1SDimitry Andric     if (RingMask + 1 != RingSize)
110*0fca6ea1SDimitry Andric       return false;
111*0fca6ea1SDimitry Andric 
112*0fca6ea1SDimitry Andric     if (TabMask == 0)
113*0fca6ea1SDimitry Andric       return false;
114*0fca6ea1SDimitry Andric     uptr TabSize = TabMask + 1;
115*0fca6ea1SDimitry Andric     if (!isPowerOfTwo(TabSize))
116*0fca6ea1SDimitry Andric       return false;
117*0fca6ea1SDimitry Andric     uptr TabBytes = sizeof(atomic_u32) * TabSize;
118*0fca6ea1SDimitry Andric 
119*0fca6ea1SDimitry Andric     // Subtract and detect underflow.
120*0fca6ea1SDimitry Andric     if (BufSize < sizeof(StackDepot))
121*0fca6ea1SDimitry Andric       return false;
122*0fca6ea1SDimitry Andric     BufSize -= sizeof(StackDepot);
123*0fca6ea1SDimitry Andric     if (BufSize < TabBytes)
124*0fca6ea1SDimitry Andric       return false;
125*0fca6ea1SDimitry Andric     BufSize -= TabBytes;
126*0fca6ea1SDimitry Andric     if (BufSize < RingBytes)
127*0fca6ea1SDimitry Andric       return false;
128*0fca6ea1SDimitry Andric     return BufSize == RingBytes;
129*0fca6ea1SDimitry Andric   }
130*0fca6ea1SDimitry Andric 
1315ffd83dbSDimitry Andric   // Insert hash of the stack trace [Begin, End) into the stack depot, and
1325ffd83dbSDimitry Andric   // return the hash.
insert(uptr * Begin,uptr * End)1335ffd83dbSDimitry Andric   u32 insert(uptr *Begin, uptr *End) {
134*0fca6ea1SDimitry Andric     auto *Tab = getTab();
135*0fca6ea1SDimitry Andric     auto *Ring = getRing();
136*0fca6ea1SDimitry Andric 
1375ffd83dbSDimitry Andric     MurMur2HashBuilder B;
1385ffd83dbSDimitry Andric     for (uptr *I = Begin; I != End; ++I)
1395ffd83dbSDimitry Andric       B.add(u32(*I) >> 2);
1405ffd83dbSDimitry Andric     u32 Hash = B.get();
1415ffd83dbSDimitry Andric 
1425ffd83dbSDimitry Andric     u32 Pos = Hash & TabMask;
1435ffd83dbSDimitry Andric     u32 RingPos = atomic_load_relaxed(&Tab[Pos]);
1445ffd83dbSDimitry Andric     u64 Entry = atomic_load_relaxed(&Ring[RingPos]);
1455ffd83dbSDimitry Andric     u64 Id = (u64(End - Begin) << 33) | (u64(Hash) << 1) | 1;
1465ffd83dbSDimitry Andric     if (Entry == Id)
1475ffd83dbSDimitry Andric       return Hash;
1485ffd83dbSDimitry Andric 
1495ffd83dbSDimitry Andric     ScopedLock Lock(RingEndMu);
1505ffd83dbSDimitry Andric     RingPos = RingEnd;
1515ffd83dbSDimitry Andric     atomic_store_relaxed(&Tab[Pos], RingPos);
1525ffd83dbSDimitry Andric     atomic_store_relaxed(&Ring[RingPos], Id);
1535ffd83dbSDimitry Andric     for (uptr *I = Begin; I != End; ++I) {
1545ffd83dbSDimitry Andric       RingPos = (RingPos + 1) & RingMask;
1555ffd83dbSDimitry Andric       atomic_store_relaxed(&Ring[RingPos], *I);
1565ffd83dbSDimitry Andric     }
1575ffd83dbSDimitry Andric     RingEnd = (RingPos + 1) & RingMask;
1585ffd83dbSDimitry Andric     return Hash;
1595ffd83dbSDimitry Andric   }
1605ffd83dbSDimitry Andric 
1615ffd83dbSDimitry Andric   // Look up a stack trace by hash. Returns true if successful. The trace may be
1625ffd83dbSDimitry Andric   // accessed via operator[] passing indexes between *RingPosPtr and
1635ffd83dbSDimitry Andric   // *RingPosPtr + *SizePtr.
find(u32 Hash,uptr * RingPosPtr,uptr * SizePtr)1645ffd83dbSDimitry Andric   bool find(u32 Hash, uptr *RingPosPtr, uptr *SizePtr) const {
165*0fca6ea1SDimitry Andric     auto *Tab = getTab();
166*0fca6ea1SDimitry Andric     auto *Ring = getRing();
167*0fca6ea1SDimitry Andric 
1685ffd83dbSDimitry Andric     u32 Pos = Hash & TabMask;
1695ffd83dbSDimitry Andric     u32 RingPos = atomic_load_relaxed(&Tab[Pos]);
1705ffd83dbSDimitry Andric     if (RingPos >= RingSize)
1715ffd83dbSDimitry Andric       return false;
1725ffd83dbSDimitry Andric     u64 Entry = atomic_load_relaxed(&Ring[RingPos]);
1735ffd83dbSDimitry Andric     u64 HashWithTagBit = (u64(Hash) << 1) | 1;
1745ffd83dbSDimitry Andric     if ((Entry & 0x1ffffffff) != HashWithTagBit)
1755ffd83dbSDimitry Andric       return false;
1765ffd83dbSDimitry Andric     u32 Size = u32(Entry >> 33);
1775ffd83dbSDimitry Andric     if (Size >= RingSize)
1785ffd83dbSDimitry Andric       return false;
1795ffd83dbSDimitry Andric     *RingPosPtr = (RingPos + 1) & RingMask;
1805ffd83dbSDimitry Andric     *SizePtr = Size;
1815ffd83dbSDimitry Andric     MurMur2HashBuilder B;
1825ffd83dbSDimitry Andric     for (uptr I = 0; I != Size; ++I) {
1835ffd83dbSDimitry Andric       RingPos = (RingPos + 1) & RingMask;
1845ffd83dbSDimitry Andric       B.add(u32(atomic_load_relaxed(&Ring[RingPos])) >> 2);
1855ffd83dbSDimitry Andric     }
1865ffd83dbSDimitry Andric     return B.get() == Hash;
1875ffd83dbSDimitry Andric   }
1885ffd83dbSDimitry Andric 
at(uptr RingPos)189*0fca6ea1SDimitry Andric   u64 at(uptr RingPos) const {
190*0fca6ea1SDimitry Andric     auto *Ring = getRing();
1915ffd83dbSDimitry Andric     return atomic_load_relaxed(&Ring[RingPos & RingMask]);
1925ffd83dbSDimitry Andric   }
193*0fca6ea1SDimitry Andric 
194*0fca6ea1SDimitry Andric   // This is done for the purpose of fork safety in multithreaded programs and
195*0fca6ea1SDimitry Andric   // does not fully disable StackDepot. In particular, find() still works and
196*0fca6ea1SDimitry Andric   // only insert() is blocked.
disable()197*0fca6ea1SDimitry Andric   void disable() NO_THREAD_SAFETY_ANALYSIS { RingEndMu.lock(); }
198*0fca6ea1SDimitry Andric 
enable()199*0fca6ea1SDimitry Andric   void enable() NO_THREAD_SAFETY_ANALYSIS { RingEndMu.unlock(); }
2005ffd83dbSDimitry Andric };
2015ffd83dbSDimitry Andric 
202*0fca6ea1SDimitry Andric // We need StackDepot to be aligned to 8-bytes so the ring we store after
203*0fca6ea1SDimitry Andric // is correctly assigned.
204*0fca6ea1SDimitry Andric static_assert(sizeof(StackDepot) % alignof(atomic_u64) == 0);
205*0fca6ea1SDimitry Andric 
2065ffd83dbSDimitry Andric } // namespace scudo
2075ffd83dbSDimitry Andric 
2085ffd83dbSDimitry Andric #endif // SCUDO_STACK_DEPOT_H_
209