Skip to content

Commit 71dcc33

Browse files
author
Kostya Kortchinsky
committed
[scudo] Lay the foundation for 32-bit support
Summary: In order to support 32-bit platforms, we have to make some adjustments in multiple locations, one of them being the Scudo chunk header. For it to fit on 64 bits (as a reminder, on x64 it's 128 bits), I had to crunch the space taken by some of the fields. In order to keep the offset field small, the secondary allocator was changed to accomodate aligned allocations for larger alignments, hence making the offset constant for chunks serviced by it. The resulting header candidate has been added, and further modifications to allow 32-bit support will follow. Another notable change is the addition of MaybeStartBackgroudThread() to allow release of the memory to the OS. Reviewers: kcc Subscribers: llvm-commits Differential Revision: https://reviews.llvm.org/D25688 llvm-svn: 285209
1 parent 8e075fd commit 71dcc33

File tree

4 files changed

+143
-86
lines changed

4 files changed

+143
-86
lines changed

compiler-rt/lib/scudo/scudo_allocator.cpp

+39-69
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
#include "scudo_allocator.h"
1818
#include "scudo_utils.h"
19-
#include "scudo_allocator_secondary.h"
2019

2120
#include "sanitizer_common/sanitizer_allocator_interface.h"
2221
#include "sanitizer_common/sanitizer_quarantine.h"
@@ -25,14 +24,10 @@
2524
#include <pthread.h>
2625
#include <smmintrin.h>
2726

28-
#include <atomic>
2927
#include <cstring>
3028

3129
namespace __scudo {
3230

33-
const uptr MinAlignmentLog = 4; // 16 bytes for x64
34-
const uptr MaxAlignmentLog = 24;
35-
3631
struct AP {
3732
static const uptr kSpaceBeg = ~0ULL;
3833
static const uptr kSpaceSize = 0x10000000000ULL;
@@ -55,55 +50,18 @@ static thread_local Xorshift128Plus Prng;
5550
// Global static cookie, initialized at start-up.
5651
static u64 Cookie;
5752

58-
enum ChunkState : u8 {
59-
ChunkAvailable = 0,
60-
ChunkAllocated = 1,
61-
ChunkQuarantine = 2
62-
};
63-
64-
typedef unsigned __int128 PackedHeader;
65-
typedef std::atomic<PackedHeader> AtomicPackedHeader;
66-
67-
// Our header requires 128-bit of storage on x64 (the only platform supported
68-
// as of now), which fits nicely with the alignment requirements.
69-
// Having the offset saves us from using functions such as GetBlockBegin, that
70-
// is fairly costly. Our first implementation used the MetaData as well, which
71-
// offers the advantage of being stored away from the chunk itself, but
72-
// accessing it was costly as well.
73-
// The header will be atomically loaded and stored using the 16-byte primitives
74-
// offered by the platform (likely requires cmpxchg16b support).
75-
struct UnpackedHeader {
76-
// 1st 8 bytes
77-
u16 Checksum : 16;
78-
u64 RequestedSize : 40; // Needed for reallocation purposes.
79-
u8 State : 2; // available, allocated, or quarantined
80-
u8 AllocType : 2; // malloc, new, new[], or memalign
81-
u8 Unused_0_ : 4;
82-
// 2nd 8 bytes
83-
u64 Offset : 20; // Offset from the beginning of the backend
84-
// allocation to the beginning of the chunk itself,
85-
// in multiples of MinAlignment. See comment about
86-
// its maximum value and test in init().
87-
u64 Unused_1_ : 28;
88-
u16 Salt : 16;
89-
};
90-
91-
COMPILER_CHECK(sizeof(UnpackedHeader) == sizeof(PackedHeader));
92-
93-
const uptr ChunkHeaderSize = sizeof(PackedHeader);
94-
9553
struct ScudoChunk : UnpackedHeader {
9654
// We can't use the offset member of the chunk itself, as we would double
9755
// fetch it without any warranty that it wouldn't have been tampered. To
9856
// prevent this, we work with a local copy of the header.
99-
void *AllocBeg(UnpackedHeader *Header) {
57+
void *getAllocBeg(UnpackedHeader *Header) {
10058
return reinterpret_cast<void *>(
10159
reinterpret_cast<uptr>(this) - (Header->Offset << MinAlignmentLog));
10260
}
10361

10462
// CRC32 checksum of the Chunk pointer and its ChunkHeader.
10563
// It currently uses the Intel Nehalem SSE4.2 crc32 64-bit instruction.
106-
u16 Checksum(UnpackedHeader *Header) const {
64+
u16 computeChecksum(UnpackedHeader *Header) const {
10765
u64 HeaderHolder[2];
10866
memcpy(HeaderHolder, Header, sizeof(HeaderHolder));
10967
u64 Crc = _mm_crc32_u64(Cookie, reinterpret_cast<uptr>(this));
@@ -125,14 +83,14 @@ struct ScudoChunk : UnpackedHeader {
12583
*NewUnpackedHeader = bit_cast<UnpackedHeader>(NewPackedHeader);
12684
if ((NewUnpackedHeader->Unused_0_ != 0) ||
12785
(NewUnpackedHeader->Unused_1_ != 0) ||
128-
(NewUnpackedHeader->Checksum != Checksum(NewUnpackedHeader))) {
86+
(NewUnpackedHeader->Checksum != computeChecksum(NewUnpackedHeader))) {
12987
dieWithMessage("ERROR: corrupted chunk header at address %p\n", this);
13088
}
13189
}
13290

13391
// Packs and stores the header, computing the checksum in the process.
13492
void storeHeader(UnpackedHeader *NewUnpackedHeader) {
135-
NewUnpackedHeader->Checksum = Checksum(NewUnpackedHeader);
93+
NewUnpackedHeader->Checksum = computeChecksum(NewUnpackedHeader);
13694
PackedHeader NewPackedHeader = bit_cast<PackedHeader>(*NewUnpackedHeader);
13795
AtomicPackedHeader *AtomicHeader =
13896
reinterpret_cast<AtomicPackedHeader *>(this);
@@ -144,7 +102,7 @@ struct ScudoChunk : UnpackedHeader {
144102
// we are not being raced by a corruption occurring in another thread.
145103
void compareExchangeHeader(UnpackedHeader *NewUnpackedHeader,
146104
UnpackedHeader *OldUnpackedHeader) {
147-
NewUnpackedHeader->Checksum = Checksum(NewUnpackedHeader);
105+
NewUnpackedHeader->Checksum = computeChecksum(NewUnpackedHeader);
148106
PackedHeader NewPackedHeader = bit_cast<PackedHeader>(*NewUnpackedHeader);
149107
PackedHeader OldPackedHeader = bit_cast<PackedHeader>(*OldUnpackedHeader);
150108
AtomicPackedHeader *AtomicHeader =
@@ -194,6 +152,8 @@ static void initInternal() {
194152
Options.setFrom(getFlags(), common_flags());
195153
initAllocator(Options);
196154

155+
MaybeStartBackgroudThread();
156+
197157
ScudoInitIsRunning = false;
198158
}
199159

@@ -221,7 +181,7 @@ struct QuarantineCallback {
221181
dieWithMessage("ERROR: invalid chunk state when recycling address %p\n",
222182
Chunk);
223183
}
224-
void *Ptr = Chunk->AllocBeg(&Header);
184+
void *Ptr = Chunk->getAllocBeg(&Header);
225185
getAllocator().Deallocate(Cache_, Ptr);
226186
}
227187

@@ -269,9 +229,8 @@ void AllocatorOptions::copyTo(Flags *f, CommonFlags *cf) const {
269229
}
270230

271231
struct Allocator {
272-
static const uptr MaxAllowedMallocSize = 1ULL << 40;
273-
static const uptr MinAlignment = 1 << MinAlignmentLog;
274-
static const uptr MaxAlignment = 1 << MaxAlignmentLog; // 16 MB
232+
static const uptr MaxAllowedMallocSize =
233+
FIRST_32_SECOND_64(2UL << 30, 1ULL << 40);
275234

276235
ScudoAllocator BackendAllocator;
277236
ScudoQuarantine AllocatorQuarantine;
@@ -296,13 +255,18 @@ struct Allocator {
296255
CHECK(testCPUFeature(SSE4_2)); // for crc32
297256

298257
// Verify that the header offset field can hold the maximum offset. In the
299-
// worst case scenario, the backend allocation is already aligned on
300-
// MaxAlignment, so in order to store the header and still be aligned, we
301-
// add an extra MaxAlignment. As a result, the offset from the beginning of
302-
// the backend allocation to the chunk will be MaxAlignment -
303-
// ChunkHeaderSize.
258+
// case of the Secondary allocator, it takes care of alignment and the
259+
// offset will always be 0. In the case of the Primary, the worst case
260+
// scenario happens in the last size class, when the backend allocation
261+
// would already be aligned on the requested alignment, which would happen
262+
// to be the maximum alignment that would fit in that size class. As a
263+
// result, the maximum offset will be at most the maximum alignment for the
264+
// last size class minus the header size, in multiples of MinAlignment.
304265
UnpackedHeader Header = {};
305-
uptr MaximumOffset = (MaxAlignment - ChunkHeaderSize) >> MinAlignmentLog;
266+
uptr MaxPrimaryAlignment = 1 << MostSignificantSetBitIndex(
267+
PrimaryAllocator::SizeClassMap::kMaxSize - MinAlignment);
268+
uptr MaximumOffset = (MaxPrimaryAlignment - ChunkHeaderSize) >>
269+
MinAlignmentLog;
306270
Header.Offset = MaximumOffset;
307271
if (Header.Offset != MaximumOffset) {
308272
dieWithMessage("ERROR: the maximum possible offset doesn't fit in the "
@@ -313,9 +277,9 @@ struct Allocator {
313277
DeleteSizeMismatch = Options.DeleteSizeMismatch;
314278
ZeroContents = Options.ZeroContents;
315279
BackendAllocator.Init(Options.MayReturnNull);
316-
AllocatorQuarantine.Init(static_cast<uptr>(Options.QuarantineSizeMb) << 20,
317-
static_cast<uptr>(
318-
Options.ThreadLocalQuarantineSizeKb) << 10);
280+
AllocatorQuarantine.Init(
281+
static_cast<uptr>(Options.QuarantineSizeMb) << 20,
282+
static_cast<uptr>(Options.ThreadLocalQuarantineSizeKb) << 10);
319283
BackendAllocator.InitCache(&FallbackAllocatorCache);
320284
Cookie = Prng.Next();
321285
}
@@ -325,7 +289,7 @@ struct Allocator {
325289
if (UNLIKELY(!ThreadInited))
326290
initThread();
327291
if (!IsPowerOfTwo(Alignment)) {
328-
dieWithMessage("ERROR: malloc alignment is not a power of 2\n");
292+
dieWithMessage("ERROR: alignment is not a power of 2\n");
329293
}
330294
if (Alignment > MaxAlignment)
331295
return BackendAllocator.ReturnNullOrDieOnBadRequest();
@@ -336,20 +300,21 @@ struct Allocator {
336300
if (Size >= MaxAllowedMallocSize)
337301
return BackendAllocator.ReturnNullOrDieOnBadRequest();
338302
uptr RoundedSize = RoundUpTo(Size, MinAlignment);
339-
uptr ExtraBytes = ChunkHeaderSize;
303+
uptr NeededSize = RoundedSize + ChunkHeaderSize;
340304
if (Alignment > MinAlignment)
341-
ExtraBytes += Alignment;
342-
uptr NeededSize = RoundedSize + ExtraBytes;
305+
NeededSize += Alignment;
343306
if (NeededSize >= MaxAllowedMallocSize)
344307
return BackendAllocator.ReturnNullOrDieOnBadRequest();
308+
bool FromPrimary = PrimaryAllocator::CanAllocate(NeededSize, MinAlignment);
345309

346310
void *Ptr;
347311
if (LIKELY(!ThreadTornDown)) {
348-
Ptr = BackendAllocator.Allocate(&Cache, NeededSize, MinAlignment);
312+
Ptr = BackendAllocator.Allocate(&Cache, NeededSize,
313+
FromPrimary ? MinAlignment : Alignment);
349314
} else {
350315
SpinMutexLock l(&FallbackMutex);
351316
Ptr = BackendAllocator.Allocate(&FallbackAllocatorCache, NeededSize,
352-
MinAlignment);
317+
FromPrimary ? MinAlignment : Alignment);
353318
}
354319
if (!Ptr)
355320
return BackendAllocator.ReturnNullOrDieOnOOM();
@@ -359,6 +324,11 @@ struct Allocator {
359324
memset(Ptr, 0, BackendAllocator.GetActuallyAllocatedSize(Ptr));
360325

361326
uptr AllocBeg = reinterpret_cast<uptr>(Ptr);
327+
// If the allocation was serviced by the secondary, the returned pointer
328+
// accounts for ChunkHeaderSize to pass the alignment check of the combined
329+
// allocator. Adjust it here.
330+
if (!FromPrimary)
331+
AllocBeg -= ChunkHeaderSize;
362332
uptr ChunkBeg = AllocBeg + ChunkHeaderSize;
363333
if (!IsAligned(ChunkBeg, Alignment))
364334
ChunkBeg = RoundUpTo(ChunkBeg, Alignment);
@@ -450,7 +420,7 @@ struct Allocator {
450420
"address %p\n", Chunk);
451421
}
452422
uptr Size =
453-
BackendAllocator.GetActuallyAllocatedSize(Chunk->AllocBeg(Header));
423+
BackendAllocator.GetActuallyAllocatedSize(Chunk->getAllocBeg(Header));
454424
// UsableSize works as malloc_usable_size, which is also what (AFAIU)
455425
// tcmalloc's MallocExtension::GetAllocatedSize aims at providing. This
456426
// means we will return the size of the chunk from the user beginning to
@@ -543,7 +513,7 @@ void drainQuarantine() {
543513
}
544514

545515
void *scudoMalloc(uptr Size, AllocType Type) {
546-
return Instance.allocate(Size, Allocator::MinAlignment, Type);
516+
return Instance.allocate(Size, MinAlignment, Type);
547517
}
548518

549519
void scudoFree(void *Ptr, AllocType Type) {
@@ -556,7 +526,7 @@ void scudoSizedFree(void *Ptr, uptr Size, AllocType Type) {
556526

557527
void *scudoRealloc(void *Ptr, uptr Size) {
558528
if (!Ptr)
559-
return Instance.allocate(Size, Allocator::MinAlignment, FromMalloc);
529+
return Instance.allocate(Size, MinAlignment, FromMalloc);
560530
if (Size == 0) {
561531
Instance.deallocate(Ptr, 0, FromMalloc);
562532
return nullptr;

compiler-rt/lib/scudo/scudo_allocator.h

+61
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222

2323
#include "sanitizer_common/sanitizer_allocator.h"
2424

25+
#include <atomic>
26+
2527
namespace __scudo {
2628

2729
enum AllocType : u8 {
@@ -31,6 +33,63 @@ enum AllocType : u8 {
3133
FromMemalign = 3, // Memory block came from memalign, posix_memalign, etc.
3234
};
3335

36+
enum ChunkState : u8 {
37+
ChunkAvailable = 0,
38+
ChunkAllocated = 1,
39+
ChunkQuarantine = 2
40+
};
41+
42+
#if SANITIZER_WORDSIZE == 64
43+
// Our header requires 128 bits of storage on 64-bit platforms, which fits
44+
// nicely with the alignment requirements. Having the offset saves us from
45+
// using functions such as GetBlockBegin, that is fairly costly. Our first
46+
// implementation used the MetaData as well, which offers the advantage of
47+
// being stored away from the chunk itself, but accessing it was costly as
48+
// well. The header will be atomically loaded and stored using the 16-byte
49+
// primitives offered by the platform (likely requires cmpxchg16b support).
50+
typedef unsigned __int128 PackedHeader;
51+
struct UnpackedHeader {
52+
u16 Checksum : 16;
53+
uptr RequestedSize : 40; // Needed for reallocation purposes.
54+
u8 State : 2; // available, allocated, or quarantined
55+
u8 AllocType : 2; // malloc, new, new[], or memalign
56+
u8 Unused_0_ : 4;
57+
uptr Offset : 12; // Offset from the beginning of the backend
58+
// allocation to the beginning of the chunk itself,
59+
// in multiples of MinAlignment. See comment about
60+
// its maximum value and test in init().
61+
u64 Unused_1_ : 36;
62+
u16 Salt : 16;
63+
};
64+
#elif SANITIZER_WORDSIZE == 32
65+
// On 32-bit platforms, our header requires 64 bits.
66+
typedef u64 PackedHeader;
67+
struct UnpackedHeader {
68+
u16 Checksum : 12;
69+
uptr RequestedSize : 32; // Needed for reallocation purposes.
70+
u8 State : 2; // available, allocated, or quarantined
71+
u8 AllocType : 2; // malloc, new, new[], or memalign
72+
uptr Offset : 12; // Offset from the beginning of the backend
73+
// allocation to the beginning of the chunk itself,
74+
// in multiples of MinAlignment. See comment about
75+
// its maximum value and test in Allocator::init().
76+
u16 Salt : 4;
77+
};
78+
#else
79+
# error "Unsupported SANITIZER_WORDSIZE."
80+
#endif // SANITIZER_WORDSIZE
81+
82+
typedef std::atomic<PackedHeader> AtomicPackedHeader;
83+
COMPILER_CHECK(sizeof(UnpackedHeader) == sizeof(PackedHeader));
84+
85+
const uptr ChunkHeaderSize = sizeof(PackedHeader);
86+
87+
// Minimum alignment of 8 bytes for 32-bit, 16 for 64-bit
88+
const uptr MinAlignmentLog = FIRST_32_SECOND_64(3, 4);
89+
const uptr MaxAlignmentLog = 24; // 16 MB
90+
const uptr MinAlignment = 1 << MinAlignmentLog;
91+
const uptr MaxAlignment = 1 << MaxAlignmentLog;
92+
3493
struct AllocatorOptions {
3594
u32 QuarantineSizeMb;
3695
u32 ThreadLocalQuarantineSizeKb;
@@ -58,6 +117,8 @@ int scudoPosixMemalign(void **MemPtr, uptr Alignment, uptr Size);
58117
void *scudoAlignedAlloc(uptr Alignment, uptr Size);
59118
uptr scudoMallocUsableSize(void *Ptr);
60119

120+
#include "scudo_allocator_secondary.h"
121+
61122
} // namespace __scudo
62123

63124
#endif // SCUDO_ALLOCATOR_H_

0 commit comments

Comments
 (0)