Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/llvm-project/compiler-rt/lib/scudo/standalone/quarantine.h
35292 views
1
//===-- quarantine.h --------------------------------------------*- C++ -*-===//
2
//
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
6
//
7
//===----------------------------------------------------------------------===//
8
9
#ifndef SCUDO_QUARANTINE_H_
10
#define SCUDO_QUARANTINE_H_
11
12
#include "list.h"
13
#include "mutex.h"
14
#include "string_utils.h"
15
#include "thread_annotations.h"
16
17
namespace scudo {
18
19
struct QuarantineBatch {
20
// With the following count, a batch (and the header that protects it) occupy
21
// 4096 bytes on 32-bit platforms, and 8192 bytes on 64-bit.
22
static const u32 MaxCount = 1019;
23
QuarantineBatch *Next;
24
uptr Size;
25
u32 Count;
26
void *Batch[MaxCount];
27
28
void init(void *Ptr, uptr Size) {
29
Count = 1;
30
Batch[0] = Ptr;
31
this->Size = Size + sizeof(QuarantineBatch); // Account for the Batch Size.
32
}
33
34
// The total size of quarantined nodes recorded in this batch.
35
uptr getQuarantinedSize() const { return Size - sizeof(QuarantineBatch); }
36
37
void push_back(void *Ptr, uptr Size) {
38
DCHECK_LT(Count, MaxCount);
39
Batch[Count++] = Ptr;
40
this->Size += Size;
41
}
42
43
bool canMerge(const QuarantineBatch *const From) const {
44
return Count + From->Count <= MaxCount;
45
}
46
47
void merge(QuarantineBatch *const From) {
48
DCHECK_LE(Count + From->Count, MaxCount);
49
DCHECK_GE(Size, sizeof(QuarantineBatch));
50
51
for (uptr I = 0; I < From->Count; ++I)
52
Batch[Count + I] = From->Batch[I];
53
Count += From->Count;
54
Size += From->getQuarantinedSize();
55
56
From->Count = 0;
57
From->Size = sizeof(QuarantineBatch);
58
}
59
60
void shuffle(u32 State) { ::scudo::shuffle(Batch, Count, &State); }
61
};
62
63
static_assert(sizeof(QuarantineBatch) <= (1U << 13), ""); // 8Kb.
64
65
// Per-thread cache of memory blocks.
66
template <typename Callback> class QuarantineCache {
67
public:
68
void init() { DCHECK_EQ(atomic_load_relaxed(&Size), 0U); }
69
70
// Total memory used, including internal accounting.
71
uptr getSize() const { return atomic_load_relaxed(&Size); }
72
// Memory used for internal accounting.
73
uptr getOverheadSize() const { return List.size() * sizeof(QuarantineBatch); }
74
75
void enqueue(Callback Cb, void *Ptr, uptr Size) {
76
if (List.empty() || List.back()->Count == QuarantineBatch::MaxCount) {
77
QuarantineBatch *B =
78
reinterpret_cast<QuarantineBatch *>(Cb.allocate(sizeof(*B)));
79
DCHECK(B);
80
B->init(Ptr, Size);
81
enqueueBatch(B);
82
} else {
83
List.back()->push_back(Ptr, Size);
84
addToSize(Size);
85
}
86
}
87
88
void transfer(QuarantineCache *From) {
89
List.append_back(&From->List);
90
addToSize(From->getSize());
91
atomic_store_relaxed(&From->Size, 0);
92
}
93
94
void enqueueBatch(QuarantineBatch *B) {
95
List.push_back(B);
96
addToSize(B->Size);
97
}
98
99
QuarantineBatch *dequeueBatch() {
100
if (List.empty())
101
return nullptr;
102
QuarantineBatch *B = List.front();
103
List.pop_front();
104
subFromSize(B->Size);
105
return B;
106
}
107
108
void mergeBatches(QuarantineCache *ToDeallocate) {
109
uptr ExtractedSize = 0;
110
QuarantineBatch *Current = List.front();
111
while (Current && Current->Next) {
112
if (Current->canMerge(Current->Next)) {
113
QuarantineBatch *Extracted = Current->Next;
114
// Move all the chunks into the current batch.
115
Current->merge(Extracted);
116
DCHECK_EQ(Extracted->Count, 0);
117
DCHECK_EQ(Extracted->Size, sizeof(QuarantineBatch));
118
// Remove the next batch From the list and account for its Size.
119
List.extract(Current, Extracted);
120
ExtractedSize += Extracted->Size;
121
// Add it to deallocation list.
122
ToDeallocate->enqueueBatch(Extracted);
123
} else {
124
Current = Current->Next;
125
}
126
}
127
subFromSize(ExtractedSize);
128
}
129
130
void getStats(ScopedString *Str) const {
131
uptr BatchCount = 0;
132
uptr TotalOverheadBytes = 0;
133
uptr TotalBytes = 0;
134
uptr TotalQuarantineChunks = 0;
135
for (const QuarantineBatch &Batch : List) {
136
BatchCount++;
137
TotalBytes += Batch.Size;
138
TotalOverheadBytes += Batch.Size - Batch.getQuarantinedSize();
139
TotalQuarantineChunks += Batch.Count;
140
}
141
const uptr QuarantineChunksCapacity =
142
BatchCount * QuarantineBatch::MaxCount;
143
const uptr ChunksUsagePercent =
144
(QuarantineChunksCapacity == 0)
145
? 0
146
: TotalQuarantineChunks * 100 / QuarantineChunksCapacity;
147
const uptr TotalQuarantinedBytes = TotalBytes - TotalOverheadBytes;
148
const uptr MemoryOverheadPercent =
149
(TotalQuarantinedBytes == 0)
150
? 0
151
: TotalOverheadBytes * 100 / TotalQuarantinedBytes;
152
Str->append(
153
"Stats: Quarantine: batches: %zu; bytes: %zu (user: %zu); chunks: %zu "
154
"(capacity: %zu); %zu%% chunks used; %zu%% memory overhead\n",
155
BatchCount, TotalBytes, TotalQuarantinedBytes, TotalQuarantineChunks,
156
QuarantineChunksCapacity, ChunksUsagePercent, MemoryOverheadPercent);
157
}
158
159
private:
160
SinglyLinkedList<QuarantineBatch> List;
161
atomic_uptr Size = {};
162
163
void addToSize(uptr add) { atomic_store_relaxed(&Size, getSize() + add); }
164
void subFromSize(uptr sub) { atomic_store_relaxed(&Size, getSize() - sub); }
165
};
166
167
// The callback interface is:
168
// void Callback::recycle(Node *Ptr);
169
// void *Callback::allocate(uptr Size);
170
// void Callback::deallocate(void *Ptr);
171
template <typename Callback, typename Node> class GlobalQuarantine {
172
public:
173
typedef QuarantineCache<Callback> CacheT;
174
using ThisT = GlobalQuarantine<Callback, Node>;
175
176
void init(uptr Size, uptr CacheSize) NO_THREAD_SAFETY_ANALYSIS {
177
DCHECK(isAligned(reinterpret_cast<uptr>(this), alignof(ThisT)));
178
DCHECK_EQ(atomic_load_relaxed(&MaxSize), 0U);
179
DCHECK_EQ(atomic_load_relaxed(&MinSize), 0U);
180
DCHECK_EQ(atomic_load_relaxed(&MaxCacheSize), 0U);
181
// Thread local quarantine size can be zero only when global quarantine size
182
// is zero (it allows us to perform just one atomic read per put() call).
183
CHECK((Size == 0 && CacheSize == 0) || CacheSize != 0);
184
185
atomic_store_relaxed(&MaxSize, Size);
186
atomic_store_relaxed(&MinSize, Size / 10 * 9); // 90% of max size.
187
atomic_store_relaxed(&MaxCacheSize, CacheSize);
188
189
Cache.init();
190
}
191
192
uptr getMaxSize() const { return atomic_load_relaxed(&MaxSize); }
193
uptr getCacheSize() const { return atomic_load_relaxed(&MaxCacheSize); }
194
195
// This is supposed to be used in test only.
196
bool isEmpty() {
197
ScopedLock L(CacheMutex);
198
return Cache.getSize() == 0U;
199
}
200
201
void put(CacheT *C, Callback Cb, Node *Ptr, uptr Size) {
202
C->enqueue(Cb, Ptr, Size);
203
if (C->getSize() > getCacheSize())
204
drain(C, Cb);
205
}
206
207
void NOINLINE drain(CacheT *C, Callback Cb) EXCLUDES(CacheMutex) {
208
bool needRecycle = false;
209
{
210
ScopedLock L(CacheMutex);
211
Cache.transfer(C);
212
needRecycle = Cache.getSize() > getMaxSize();
213
}
214
215
if (needRecycle && RecycleMutex.tryLock())
216
recycle(atomic_load_relaxed(&MinSize), Cb);
217
}
218
219
void NOINLINE drainAndRecycle(CacheT *C, Callback Cb) EXCLUDES(CacheMutex) {
220
{
221
ScopedLock L(CacheMutex);
222
Cache.transfer(C);
223
}
224
RecycleMutex.lock();
225
recycle(0, Cb);
226
}
227
228
void getStats(ScopedString *Str) EXCLUDES(CacheMutex) {
229
ScopedLock L(CacheMutex);
230
// It assumes that the world is stopped, just as the allocator's printStats.
231
Cache.getStats(Str);
232
Str->append("Quarantine limits: global: %zuK; thread local: %zuK\n",
233
getMaxSize() >> 10, getCacheSize() >> 10);
234
}
235
236
void disable() NO_THREAD_SAFETY_ANALYSIS {
237
// RecycleMutex must be locked 1st since we grab CacheMutex within recycle.
238
RecycleMutex.lock();
239
CacheMutex.lock();
240
}
241
242
void enable() NO_THREAD_SAFETY_ANALYSIS {
243
CacheMutex.unlock();
244
RecycleMutex.unlock();
245
}
246
247
private:
248
// Read-only data.
249
alignas(SCUDO_CACHE_LINE_SIZE) HybridMutex CacheMutex;
250
CacheT Cache GUARDED_BY(CacheMutex);
251
alignas(SCUDO_CACHE_LINE_SIZE) HybridMutex RecycleMutex;
252
atomic_uptr MinSize = {};
253
atomic_uptr MaxSize = {};
254
alignas(SCUDO_CACHE_LINE_SIZE) atomic_uptr MaxCacheSize = {};
255
256
void NOINLINE recycle(uptr MinSize, Callback Cb) RELEASE(RecycleMutex)
257
EXCLUDES(CacheMutex) {
258
CacheT Tmp;
259
Tmp.init();
260
{
261
ScopedLock L(CacheMutex);
262
// Go over the batches and merge partially filled ones to
263
// save some memory, otherwise batches themselves (since the memory used
264
// by them is counted against quarantine limit) can overcome the actual
265
// user's quarantined chunks, which diminishes the purpose of the
266
// quarantine.
267
const uptr CacheSize = Cache.getSize();
268
const uptr OverheadSize = Cache.getOverheadSize();
269
DCHECK_GE(CacheSize, OverheadSize);
270
// Do the merge only when overhead exceeds this predefined limit (might
271
// require some tuning). It saves us merge attempt when the batch list
272
// quarantine is unlikely to contain batches suitable for merge.
273
constexpr uptr OverheadThresholdPercents = 100;
274
if (CacheSize > OverheadSize &&
275
OverheadSize * (100 + OverheadThresholdPercents) >
276
CacheSize * OverheadThresholdPercents) {
277
Cache.mergeBatches(&Tmp);
278
}
279
// Extract enough chunks from the quarantine to get below the max
280
// quarantine size and leave some leeway for the newly quarantined chunks.
281
while (Cache.getSize() > MinSize)
282
Tmp.enqueueBatch(Cache.dequeueBatch());
283
}
284
RecycleMutex.unlock();
285
doRecycle(&Tmp, Cb);
286
}
287
288
void NOINLINE doRecycle(CacheT *C, Callback Cb) {
289
while (QuarantineBatch *B = C->dequeueBatch()) {
290
const u32 Seed = static_cast<u32>(
291
(reinterpret_cast<uptr>(B) ^ reinterpret_cast<uptr>(C)) >> 4);
292
B->shuffle(Seed);
293
constexpr uptr NumberOfPrefetch = 8UL;
294
CHECK(NumberOfPrefetch <= ARRAY_SIZE(B->Batch));
295
for (uptr I = 0; I < NumberOfPrefetch; I++)
296
PREFETCH(B->Batch[I]);
297
for (uptr I = 0, Count = B->Count; I < Count; I++) {
298
if (I + NumberOfPrefetch < Count)
299
PREFETCH(B->Batch[I + NumberOfPrefetch]);
300
Cb.recycle(reinterpret_cast<Node *>(B->Batch[I]));
301
}
302
Cb.deallocate(B);
303
}
304
}
305
};
306
307
} // namespace scudo
308
309
#endif // SCUDO_QUARANTINE_H_
310
311