xref: /llvm-project/compiler-rt/lib/scudo/standalone/stack_depot.h (revision ed6edf262d9061ce3c024754c4981299b5184ee2)
121d50019SPeter Collingbourne //===-- stack_depot.h -------------------------------------------*- C++ -*-===//
221d50019SPeter Collingbourne //
321d50019SPeter Collingbourne // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
421d50019SPeter Collingbourne // See https://llvm.org/LICENSE.txt for license information.
521d50019SPeter Collingbourne // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
621d50019SPeter Collingbourne //
721d50019SPeter Collingbourne //===----------------------------------------------------------------------===//
821d50019SPeter Collingbourne 
921d50019SPeter Collingbourne #ifndef SCUDO_STACK_DEPOT_H_
1021d50019SPeter Collingbourne #define SCUDO_STACK_DEPOT_H_
1121d50019SPeter Collingbourne 
1221d50019SPeter Collingbourne #include "atomic_helpers.h"
133da01663SFlorian Mayer #include "common.h"
1421d50019SPeter Collingbourne #include "mutex.h"
1521d50019SPeter Collingbourne 
1621d50019SPeter Collingbourne namespace scudo {
1721d50019SPeter Collingbourne 
1821d50019SPeter Collingbourne class MurMur2HashBuilder {
1921d50019SPeter Collingbourne   static const u32 M = 0x5bd1e995;
2021d50019SPeter Collingbourne   static const u32 Seed = 0x9747b28c;
2121d50019SPeter Collingbourne   static const u32 R = 24;
2221d50019SPeter Collingbourne   u32 H;
2321d50019SPeter Collingbourne 
2421d50019SPeter Collingbourne public:
2521d50019SPeter Collingbourne   explicit MurMur2HashBuilder(u32 Init = 0) { H = Seed ^ Init; }
add(u32 K)2621d50019SPeter Collingbourne   void add(u32 K) {
2721d50019SPeter Collingbourne     K *= M;
2821d50019SPeter Collingbourne     K ^= K >> R;
2921d50019SPeter Collingbourne     K *= M;
3021d50019SPeter Collingbourne     H *= M;
3121d50019SPeter Collingbourne     H ^= K;
3221d50019SPeter Collingbourne   }
get()3321d50019SPeter Collingbourne   u32 get() {
3421d50019SPeter Collingbourne     u32 X = H;
3521d50019SPeter Collingbourne     X ^= X >> 13;
3621d50019SPeter Collingbourne     X *= M;
3721d50019SPeter Collingbourne     X ^= X >> 15;
3821d50019SPeter Collingbourne     return X;
3921d50019SPeter Collingbourne   }
4021d50019SPeter Collingbourne };
4121d50019SPeter Collingbourne 
423da01663SFlorian Mayer class alignas(atomic_u64) StackDepot {
4321d50019SPeter Collingbourne   HybridMutex RingEndMu;
44d56ef852SVitaly Buka   u32 RingEnd = 0;
4521d50019SPeter Collingbourne 
4621d50019SPeter Collingbourne   // This data structure stores a stack trace for each allocation and
4721d50019SPeter Collingbourne   // deallocation when stack trace recording is enabled, that may be looked up
4821d50019SPeter Collingbourne   // using a hash of the stack trace. The lower bits of the hash are an index
4921d50019SPeter Collingbourne   // into the Tab array, which stores an index into the Ring array where the
5021d50019SPeter Collingbourne   // stack traces are stored. As the name implies, Ring is a ring buffer, so a
5121d50019SPeter Collingbourne   // stack trace may wrap around to the start of the array.
5221d50019SPeter Collingbourne   //
5321d50019SPeter Collingbourne   // Each stack trace in Ring is prefixed by a stack trace marker consisting of
5421d50019SPeter Collingbourne   // a fixed 1 bit in bit 0 (this allows disambiguation between stack frames
5521d50019SPeter Collingbourne   // and stack trace markers in the case where instruction pointers are 4-byte
5621d50019SPeter Collingbourne   // aligned, as they are on arm64), the stack trace hash in bits 1-32, and the
5721d50019SPeter Collingbourne   // size of the stack trace in bits 33-63.
5821d50019SPeter Collingbourne   //
5921d50019SPeter Collingbourne   // The insert() function is potentially racy in its accesses to the Tab and
6021d50019SPeter Collingbourne   // Ring arrays, but find() is resilient to races in the sense that, barring
6121d50019SPeter Collingbourne   // hash collisions, it will either return the correct stack trace or no stack
6221d50019SPeter Collingbourne   // trace at all, even if two instances of insert() raced with one another.
6321d50019SPeter Collingbourne   // This is achieved by re-checking the hash of the stack trace before
6421d50019SPeter Collingbourne   // returning the trace.
6521d50019SPeter Collingbourne 
663da01663SFlorian Mayer   u32 RingSize = 0;
673da01663SFlorian Mayer   u32 RingMask = 0;
683da01663SFlorian Mayer   u32 TabMask = 0;
693da01663SFlorian Mayer   // This is immediately followed by RingSize atomic_u64 and
703da01663SFlorian Mayer   // (TabMask + 1) atomic_u32.
7121d50019SPeter Collingbourne 
getRing()723da01663SFlorian Mayer   atomic_u64 *getRing() {
733da01663SFlorian Mayer     return reinterpret_cast<atomic_u64 *>(reinterpret_cast<char *>(this) +
743da01663SFlorian Mayer                                           sizeof(StackDepot));
753da01663SFlorian Mayer   }
763da01663SFlorian Mayer 
getTab()773da01663SFlorian Mayer   atomic_u32 *getTab() {
783da01663SFlorian Mayer     return reinterpret_cast<atomic_u32 *>(reinterpret_cast<char *>(this) +
793da01663SFlorian Mayer                                           sizeof(StackDepot) +
803da01663SFlorian Mayer                                           sizeof(atomic_u64) * RingSize);
813da01663SFlorian Mayer   }
823da01663SFlorian Mayer 
getRing()833da01663SFlorian Mayer   const atomic_u64 *getRing() const {
843da01663SFlorian Mayer     return reinterpret_cast<const atomic_u64 *>(
853da01663SFlorian Mayer         reinterpret_cast<const char *>(this) + sizeof(StackDepot));
863da01663SFlorian Mayer   }
873da01663SFlorian Mayer 
getTab()883da01663SFlorian Mayer   const atomic_u32 *getTab() const {
893da01663SFlorian Mayer     return reinterpret_cast<const atomic_u32 *>(
903da01663SFlorian Mayer         reinterpret_cast<const char *>(this) + sizeof(StackDepot) +
913da01663SFlorian Mayer         sizeof(atomic_u64) * RingSize);
923da01663SFlorian Mayer   }
9321d50019SPeter Collingbourne 
9421d50019SPeter Collingbourne public:
init(u32 RingSz,u32 TabSz)953da01663SFlorian Mayer   void init(u32 RingSz, u32 TabSz) {
963da01663SFlorian Mayer     DCHECK(isPowerOfTwo(RingSz));
973da01663SFlorian Mayer     DCHECK(isPowerOfTwo(TabSz));
983da01663SFlorian Mayer     RingSize = RingSz;
993da01663SFlorian Mayer     RingMask = RingSz - 1;
1003da01663SFlorian Mayer     TabMask = TabSz - 1;
1013da01663SFlorian Mayer   }
1023da01663SFlorian Mayer 
1033da01663SFlorian Mayer   // Ensure that RingSize, RingMask and TabMask are set up in a way that
1043da01663SFlorian Mayer   // all accesses are within range of BufSize.
isValid(uptr BufSize)1053da01663SFlorian Mayer   bool isValid(uptr BufSize) const {
106*ed6edf26SChristopher Ferris     if (!isPowerOfTwo(RingSize))
1073da01663SFlorian Mayer       return false;
1083da01663SFlorian Mayer     uptr RingBytes = sizeof(atomic_u64) * RingSize;
1093da01663SFlorian Mayer     if (RingMask + 1 != RingSize)
1103da01663SFlorian Mayer       return false;
1113da01663SFlorian Mayer 
1123da01663SFlorian Mayer     if (TabMask == 0)
1133da01663SFlorian Mayer       return false;
1143da01663SFlorian Mayer     uptr TabSize = TabMask + 1;
115*ed6edf26SChristopher Ferris     if (!isPowerOfTwo(TabSize))
1163da01663SFlorian Mayer       return false;
1173da01663SFlorian Mayer     uptr TabBytes = sizeof(atomic_u32) * TabSize;
1183da01663SFlorian Mayer 
1193da01663SFlorian Mayer     // Subtract and detect underflow.
1203da01663SFlorian Mayer     if (BufSize < sizeof(StackDepot))
1213da01663SFlorian Mayer       return false;
1223da01663SFlorian Mayer     BufSize -= sizeof(StackDepot);
1233da01663SFlorian Mayer     if (BufSize < TabBytes)
1243da01663SFlorian Mayer       return false;
1253da01663SFlorian Mayer     BufSize -= TabBytes;
1263da01663SFlorian Mayer     if (BufSize < RingBytes)
1273da01663SFlorian Mayer       return false;
1283da01663SFlorian Mayer     return BufSize == RingBytes;
1293da01663SFlorian Mayer   }
1303da01663SFlorian Mayer 
13121d50019SPeter Collingbourne   // Insert hash of the stack trace [Begin, End) into the stack depot, and
13221d50019SPeter Collingbourne   // return the hash.
insert(uptr * Begin,uptr * End)13321d50019SPeter Collingbourne   u32 insert(uptr *Begin, uptr *End) {
1343da01663SFlorian Mayer     auto *Tab = getTab();
1353da01663SFlorian Mayer     auto *Ring = getRing();
1363da01663SFlorian Mayer 
13721d50019SPeter Collingbourne     MurMur2HashBuilder B;
13821d50019SPeter Collingbourne     for (uptr *I = Begin; I != End; ++I)
13921d50019SPeter Collingbourne       B.add(u32(*I) >> 2);
14021d50019SPeter Collingbourne     u32 Hash = B.get();
14121d50019SPeter Collingbourne 
14221d50019SPeter Collingbourne     u32 Pos = Hash & TabMask;
14321d50019SPeter Collingbourne     u32 RingPos = atomic_load_relaxed(&Tab[Pos]);
14421d50019SPeter Collingbourne     u64 Entry = atomic_load_relaxed(&Ring[RingPos]);
14521d50019SPeter Collingbourne     u64 Id = (u64(End - Begin) << 33) | (u64(Hash) << 1) | 1;
14621d50019SPeter Collingbourne     if (Entry == Id)
14721d50019SPeter Collingbourne       return Hash;
14821d50019SPeter Collingbourne 
14921d50019SPeter Collingbourne     ScopedLock Lock(RingEndMu);
15021d50019SPeter Collingbourne     RingPos = RingEnd;
15121d50019SPeter Collingbourne     atomic_store_relaxed(&Tab[Pos], RingPos);
15221d50019SPeter Collingbourne     atomic_store_relaxed(&Ring[RingPos], Id);
15321d50019SPeter Collingbourne     for (uptr *I = Begin; I != End; ++I) {
15421d50019SPeter Collingbourne       RingPos = (RingPos + 1) & RingMask;
15521d50019SPeter Collingbourne       atomic_store_relaxed(&Ring[RingPos], *I);
15621d50019SPeter Collingbourne     }
15721d50019SPeter Collingbourne     RingEnd = (RingPos + 1) & RingMask;
15821d50019SPeter Collingbourne     return Hash;
15921d50019SPeter Collingbourne   }
16021d50019SPeter Collingbourne 
16121d50019SPeter Collingbourne   // Look up a stack trace by hash. Returns true if successful. The trace may be
16221d50019SPeter Collingbourne   // accessed via operator[] passing indexes between *RingPosPtr and
16321d50019SPeter Collingbourne   // *RingPosPtr + *SizePtr.
find(u32 Hash,uptr * RingPosPtr,uptr * SizePtr)16421d50019SPeter Collingbourne   bool find(u32 Hash, uptr *RingPosPtr, uptr *SizePtr) const {
1653da01663SFlorian Mayer     auto *Tab = getTab();
1663da01663SFlorian Mayer     auto *Ring = getRing();
1673da01663SFlorian Mayer 
16821d50019SPeter Collingbourne     u32 Pos = Hash & TabMask;
16921d50019SPeter Collingbourne     u32 RingPos = atomic_load_relaxed(&Tab[Pos]);
17021d50019SPeter Collingbourne     if (RingPos >= RingSize)
17121d50019SPeter Collingbourne       return false;
17221d50019SPeter Collingbourne     u64 Entry = atomic_load_relaxed(&Ring[RingPos]);
17321d50019SPeter Collingbourne     u64 HashWithTagBit = (u64(Hash) << 1) | 1;
17421d50019SPeter Collingbourne     if ((Entry & 0x1ffffffff) != HashWithTagBit)
17521d50019SPeter Collingbourne       return false;
1761b0436cdSkpdev     u32 Size = u32(Entry >> 33);
17721d50019SPeter Collingbourne     if (Size >= RingSize)
17821d50019SPeter Collingbourne       return false;
17921d50019SPeter Collingbourne     *RingPosPtr = (RingPos + 1) & RingMask;
18021d50019SPeter Collingbourne     *SizePtr = Size;
18121d50019SPeter Collingbourne     MurMur2HashBuilder B;
18221d50019SPeter Collingbourne     for (uptr I = 0; I != Size; ++I) {
18321d50019SPeter Collingbourne       RingPos = (RingPos + 1) & RingMask;
18421d50019SPeter Collingbourne       B.add(u32(atomic_load_relaxed(&Ring[RingPos])) >> 2);
18521d50019SPeter Collingbourne     }
18621d50019SPeter Collingbourne     return B.get() == Hash;
18721d50019SPeter Collingbourne   }
18821d50019SPeter Collingbourne 
at(uptr RingPos)1893da01663SFlorian Mayer   u64 at(uptr RingPos) const {
1903da01663SFlorian Mayer     auto *Ring = getRing();
19121d50019SPeter Collingbourne     return atomic_load_relaxed(&Ring[RingPos & RingMask]);
19221d50019SPeter Collingbourne   }
193c82f3cafSEvgenii Stepanov 
194c82f3cafSEvgenii Stepanov   // This is done for the purpose of fork safety in multithreaded programs and
195c82f3cafSEvgenii Stepanov   // does not fully disable StackDepot. In particular, find() still works and
196c82f3cafSEvgenii Stepanov   // only insert() is blocked.
disable()197c82f3cafSEvgenii Stepanov   void disable() NO_THREAD_SAFETY_ANALYSIS { RingEndMu.lock(); }
198c82f3cafSEvgenii Stepanov 
enable()199c82f3cafSEvgenii Stepanov   void enable() NO_THREAD_SAFETY_ANALYSIS { RingEndMu.unlock(); }
20021d50019SPeter Collingbourne };
20121d50019SPeter Collingbourne 
202b4e08904SFlorian Mayer // We need StackDepot to be aligned to 8-bytes so the ring we store after
203b4e08904SFlorian Mayer // is correctly assigned.
204b4e08904SFlorian Mayer static_assert(sizeof(StackDepot) % alignof(atomic_u64) == 0);
205b4e08904SFlorian Mayer 
20621d50019SPeter Collingbourne } // namespace scudo
20721d50019SPeter Collingbourne 
20821d50019SPeter Collingbourne #endif // SCUDO_STACK_DEPOT_H_
209