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