Update scudo to 18.1.1

This commit is contained in:
Ilya Fedin 2024-03-18 17:57:44 +04:00 committed by John Preston
parent 07ab875655
commit 846a6d8717
65 changed files with 2424 additions and 1395 deletions

View file

@ -25,7 +25,7 @@ append_list_if(COMPILER_RT_HAS_WNO_PEDANTIC -Wno-pedantic SCUDO_CFLAGS)
append_list_if(COMPILER_RT_HAS_FNO_LTO_FLAG -fno-lto SCUDO_CFLAGS) append_list_if(COMPILER_RT_HAS_FNO_LTO_FLAG -fno-lto SCUDO_CFLAGS)
if(COMPILER_RT_DEBUG) if(COMPILER_RT_DEBUG)
list(APPEND SCUDO_CFLAGS -O0 -DSCUDO_DEBUG=1) list(APPEND SCUDO_CFLAGS -O0 -DSCUDO_DEBUG=1 -DSCUDO_ENABLE_HOOKS=1)
else() else()
list(APPEND SCUDO_CFLAGS -O3) list(APPEND SCUDO_CFLAGS -O3)
endif() endif()
@ -56,11 +56,15 @@ if(ANDROID)
endif() endif()
set(SCUDO_HEADERS set(SCUDO_HEADERS
allocator_common.h
allocator_config.h allocator_config.h
atomic_helpers.h atomic_helpers.h
bytemap.h bytemap.h
checksum.h checksum.h
chunk.h chunk.h
condition_variable.h
condition_variable_base.h
condition_variable_linux.h
combined.h combined.h
common.h common.h
flags_parser.h flags_parser.h
@ -74,6 +78,7 @@ set(SCUDO_HEADERS
mem_map.h mem_map.h
mem_map_base.h mem_map_base.h
mem_map_fuchsia.h mem_map_fuchsia.h
mem_map_linux.h
mutex.h mutex.h
options.h options.h
platform.h platform.h
@ -82,7 +87,7 @@ set(SCUDO_HEADERS
quarantine.h quarantine.h
release.h release.h
report.h report.h
rss_limit_checker.h report_linux.h
secondary.h secondary.h
size_class_map.h size_class_map.h
stack_depot.h stack_depot.h
@ -102,6 +107,7 @@ set(SCUDO_HEADERS
set(SCUDO_SOURCES set(SCUDO_SOURCES
checksum.cpp checksum.cpp
common.cpp common.cpp
condition_variable_linux.cpp
crc32_hw.cpp crc32_hw.cpp
flags_parser.cpp flags_parser.cpp
flags.cpp flags.cpp
@ -109,9 +115,10 @@ set(SCUDO_SOURCES
linux.cpp linux.cpp
mem_map.cpp mem_map.cpp
mem_map_fuchsia.cpp mem_map_fuchsia.cpp
mem_map_linux.cpp
release.cpp release.cpp
report.cpp report.cpp
rss_limit_checker.cpp report_linux.cpp
string_utils.cpp string_utils.cpp
timing.cpp timing.cpp
) )

View file

@ -0,0 +1,85 @@
//===-- allocator_common.h --------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef SCUDO_ALLOCATOR_COMMON_H_
#define SCUDO_ALLOCATOR_COMMON_H_
#include "common.h"
#include "list.h"
namespace scudo {
template <class SizeClassAllocator> struct TransferBatch {
typedef typename SizeClassAllocator::SizeClassMap SizeClassMap;
typedef typename SizeClassAllocator::CompactPtrT CompactPtrT;
static const u16 MaxNumCached = SizeClassMap::MaxNumCachedHint;
void setFromArray(CompactPtrT *Array, u16 N) {
DCHECK_LE(N, MaxNumCached);
Count = N;
memcpy(Batch, Array, sizeof(Batch[0]) * Count);
}
void appendFromArray(CompactPtrT *Array, u16 N) {
DCHECK_LE(N, MaxNumCached - Count);
memcpy(Batch + Count, Array, sizeof(Batch[0]) * N);
// u16 will be promoted to int by arithmetic type conversion.
Count = static_cast<u16>(Count + N);
}
void appendFromTransferBatch(TransferBatch *B, u16 N) {
DCHECK_LE(N, MaxNumCached - Count);
DCHECK_GE(B->Count, N);
// Append from the back of `B`.
memcpy(Batch + Count, B->Batch + (B->Count - N), sizeof(Batch[0]) * N);
// u16 will be promoted to int by arithmetic type conversion.
Count = static_cast<u16>(Count + N);
B->Count = static_cast<u16>(B->Count - N);
}
void clear() { Count = 0; }
void add(CompactPtrT P) {
DCHECK_LT(Count, MaxNumCached);
Batch[Count++] = P;
}
void moveToArray(CompactPtrT *Array) {
memcpy(Array, Batch, sizeof(Batch[0]) * Count);
clear();
}
u16 getCount() const { return Count; }
bool isEmpty() const { return Count == 0U; }
CompactPtrT get(u16 I) const {
DCHECK_LE(I, Count);
return Batch[I];
}
TransferBatch *Next;
private:
CompactPtrT Batch[MaxNumCached];
u16 Count;
};
// A BatchGroup is used to collect blocks. Each group has a group id to
// identify the group kind of contained blocks.
template <class SizeClassAllocator> struct BatchGroup {
// `Next` is used by IntrusiveList.
BatchGroup *Next;
// The compact base address of each group
uptr CompactPtrGroupBase;
// Cache value of SizeClassAllocatorLocalCache::getMaxCached()
u16 MaxCachedPerBatch;
// Number of blocks pushed into this group. This is an increment-only
// counter.
uptr PushedBlocks;
// This is used to track how many bytes are not in-use since last time we
// tried to release pages.
uptr BytesInBGAtLastCheckpoint;
// Blocks are managed by TransferBatch in a list.
SinglyLinkedList<TransferBatch<SizeClassAllocator>> Batches;
};
} // namespace scudo
#endif // SCUDO_ALLOCATOR_COMMON_H_

View file

@ -11,6 +11,7 @@
#include "combined.h" #include "combined.h"
#include "common.h" #include "common.h"
#include "condition_variable.h"
#include "flags.h" #include "flags.h"
#include "primary32.h" #include "primary32.h"
#include "primary64.h" #include "primary64.h"
@ -82,6 +83,14 @@ namespace scudo {
// // Defines the minimal & maximal release interval that can be set. // // Defines the minimal & maximal release interval that can be set.
// static const s32 MinReleaseToOsIntervalMs = INT32_MIN; // static const s32 MinReleaseToOsIntervalMs = INT32_MIN;
// static const s32 MaxReleaseToOsIntervalMs = INT32_MAX; // static const s32 MaxReleaseToOsIntervalMs = INT32_MAX;
//
// // Use condition variable to shorten the waiting time of refillment of
// // freelist. Note that this depends on the implementation of condition
// // variable on each platform and the performance may vary so that it
// // doesn't guarantee a performance benefit.
// // Note that both variables have to be defined to enable it.
// static const bool UseConditionVariable = true;
// using ConditionVariableT = ConditionVariableLinux;
// }; // };
// // Defines the type of Primary allocator to use. // // Defines the type of Primary allocator to use.
// template <typename Config> using PrimaryT = SizeClassAllocator64<Config>; // template <typename Config> using PrimaryT = SizeClassAllocator64<Config>;
@ -195,50 +204,6 @@ struct AndroidConfig {
template <typename Config> using SecondaryT = MapAllocator<Config>; template <typename Config> using SecondaryT = MapAllocator<Config>;
}; };
struct AndroidSvelteConfig {
static const bool MaySupportMemoryTagging = false;
template <class A>
using TSDRegistryT = TSDRegistrySharedT<A, 2U, 1U>; // Shared, max 2 TSDs.
struct Primary {
using SizeClassMap = SvelteSizeClassMap;
#if SCUDO_CAN_USE_PRIMARY64
static const uptr RegionSizeLog = 27U;
typedef u32 CompactPtrT;
static const uptr CompactPtrScale = SCUDO_MIN_ALIGNMENT_LOG;
static const uptr GroupSizeLog = 18U;
static const bool EnableRandomOffset = true;
static const uptr MapSizeIncrement = 1UL << 18;
#else
static const uptr RegionSizeLog = 16U;
static const uptr GroupSizeLog = 16U;
typedef uptr CompactPtrT;
#endif
static const s32 MinReleaseToOsIntervalMs = 1000;
static const s32 MaxReleaseToOsIntervalMs = 1000;
};
#if SCUDO_CAN_USE_PRIMARY64
template <typename Config> using PrimaryT = SizeClassAllocator64<Config>;
#else
template <typename Config> using PrimaryT = SizeClassAllocator32<Config>;
#endif
struct Secondary {
struct Cache {
static const u32 EntriesArraySize = 16U;
static const u32 QuarantineSize = 32U;
static const u32 DefaultMaxEntriesCount = 4U;
static const uptr DefaultMaxEntrySize = 1UL << 18;
static const s32 MinReleaseToOsIntervalMs = 0;
static const s32 MaxReleaseToOsIntervalMs = 0;
};
template <typename Config> using CacheT = MapAllocatorCache<Config>;
};
template <typename Config> using SecondaryT = MapAllocator<Config>;
};
#if SCUDO_CAN_USE_PRIMARY64 #if SCUDO_CAN_USE_PRIMARY64
struct FuchsiaConfig { struct FuchsiaConfig {
static const bool MaySupportMemoryTagging = false; static const bool MaySupportMemoryTagging = false;

View file

@ -133,10 +133,10 @@ inline void atomic_store_relaxed(volatile T *A, typename T::Type V) {
} }
template <typename T> template <typename T>
inline typename T::Type atomic_compare_exchange(volatile T *A, inline typename T::Type
typename T::Type Cmp, atomic_compare_exchange_strong(volatile T *A, typename T::Type Cmp,
typename T::Type Xchg) { typename T::Type Xchg, memory_order MO) {
atomic_compare_exchange_strong(A, &Cmp, Xchg, memory_order_acquire); atomic_compare_exchange_strong(A, &Cmp, Xchg, MO);
return Cmp; return Cmp;
} }

View file

@ -52,8 +52,6 @@ static const size_t MaxSize = 128 * 1024;
// cleanly. // cleanly.
BENCHMARK_TEMPLATE(BM_malloc_free, scudo::AndroidConfig) BENCHMARK_TEMPLATE(BM_malloc_free, scudo::AndroidConfig)
->Range(MinSize, MaxSize); ->Range(MinSize, MaxSize);
BENCHMARK_TEMPLATE(BM_malloc_free, scudo::AndroidSvelteConfig)
->Range(MinSize, MaxSize);
#if SCUDO_CAN_USE_PRIMARY64 #if SCUDO_CAN_USE_PRIMARY64
BENCHMARK_TEMPLATE(BM_malloc_free, scudo::FuchsiaConfig) BENCHMARK_TEMPLATE(BM_malloc_free, scudo::FuchsiaConfig)
->Range(MinSize, MaxSize); ->Range(MinSize, MaxSize);
@ -99,8 +97,6 @@ static const size_t MaxIters = 32 * 1024;
// cleanly. // cleanly.
BENCHMARK_TEMPLATE(BM_malloc_free_loop, scudo::AndroidConfig) BENCHMARK_TEMPLATE(BM_malloc_free_loop, scudo::AndroidConfig)
->Range(MinIters, MaxIters); ->Range(MinIters, MaxIters);
BENCHMARK_TEMPLATE(BM_malloc_free_loop, scudo::AndroidSvelteConfig)
->Range(MinIters, MaxIters);
#if SCUDO_CAN_USE_PRIMARY64 #if SCUDO_CAN_USE_PRIMARY64
BENCHMARK_TEMPLATE(BM_malloc_free_loop, scudo::FuchsiaConfig) BENCHMARK_TEMPLATE(BM_malloc_free_loop, scudo::FuchsiaConfig)
->Range(MinIters, MaxIters); ->Range(MinIters, MaxIters);

View file

@ -128,19 +128,6 @@ inline void loadHeader(u32 Cookie, const void *Ptr,
reportHeaderCorruption(const_cast<void *>(Ptr)); reportHeaderCorruption(const_cast<void *>(Ptr));
} }
inline void compareExchangeHeader(u32 Cookie, void *Ptr,
UnpackedHeader *NewUnpackedHeader,
UnpackedHeader *OldUnpackedHeader) {
NewUnpackedHeader->Checksum =
computeHeaderChecksum(Cookie, Ptr, NewUnpackedHeader);
PackedHeader NewPackedHeader = bit_cast<PackedHeader>(*NewUnpackedHeader);
PackedHeader OldPackedHeader = bit_cast<PackedHeader>(*OldUnpackedHeader);
if (UNLIKELY(!atomic_compare_exchange_strong(
getAtomicHeader(Ptr), &OldPackedHeader, NewPackedHeader,
memory_order_relaxed)))
reportHeaderRace(Ptr);
}
inline bool isValid(u32 Cookie, const void *Ptr, inline bool isValid(u32 Cookie, const void *Ptr,
UnpackedHeader *NewUnpackedHeader) { UnpackedHeader *NewUnpackedHeader) {
PackedHeader NewPackedHeader = atomic_load_relaxed(getConstAtomicHeader(Ptr)); PackedHeader NewPackedHeader = atomic_load_relaxed(getConstAtomicHeader(Ptr));

View file

@ -14,11 +14,11 @@
#include "flags.h" #include "flags.h"
#include "flags_parser.h" #include "flags_parser.h"
#include "local_cache.h" #include "local_cache.h"
#include "mem_map.h"
#include "memtag.h" #include "memtag.h"
#include "options.h" #include "options.h"
#include "quarantine.h" #include "quarantine.h"
#include "report.h" #include "report.h"
#include "rss_limit_checker.h"
#include "secondary.h" #include "secondary.h"
#include "stack_depot.h" #include "stack_depot.h"
#include "string_utils.h" #include "string_utils.h"
@ -68,14 +68,13 @@ public:
if (UNLIKELY(Header.State != Chunk::State::Quarantined)) if (UNLIKELY(Header.State != Chunk::State::Quarantined))
reportInvalidChunkState(AllocatorAction::Recycling, Ptr); reportInvalidChunkState(AllocatorAction::Recycling, Ptr);
Chunk::UnpackedHeader NewHeader = Header; Header.State = Chunk::State::Available;
NewHeader.State = Chunk::State::Available; Chunk::storeHeader(Allocator.Cookie, Ptr, &Header);
Chunk::compareExchangeHeader(Allocator.Cookie, Ptr, &NewHeader, &Header);
if (allocatorSupportsMemoryTagging<Config>()) if (allocatorSupportsMemoryTagging<Config>())
Ptr = untagPointer(Ptr); Ptr = untagPointer(Ptr);
void *BlockBegin = Allocator::getBlockBegin(Ptr, &NewHeader); void *BlockBegin = Allocator::getBlockBegin(Ptr, &Header);
Cache.deallocate(NewHeader.ClassId, BlockBegin); Cache.deallocate(Header.ClassId, BlockBegin);
} }
// We take a shortcut when allocating a quarantine batch by working with the // We take a shortcut when allocating a quarantine batch by working with the
@ -118,9 +117,8 @@ public:
DCHECK_EQ(Header.Offset, 0); DCHECK_EQ(Header.Offset, 0);
DCHECK_EQ(Header.SizeOrUnusedBytes, sizeof(QuarantineBatch)); DCHECK_EQ(Header.SizeOrUnusedBytes, sizeof(QuarantineBatch));
Chunk::UnpackedHeader NewHeader = Header; Header.State = Chunk::State::Available;
NewHeader.State = Chunk::State::Available; Chunk::storeHeader(Allocator.Cookie, Ptr, &Header);
Chunk::compareExchangeHeader(Allocator.Cookie, Ptr, &NewHeader, &Header);
Cache.deallocate(QuarantineClassId, Cache.deallocate(QuarantineClassId,
reinterpret_cast<void *>(reinterpret_cast<uptr>(Ptr) - reinterpret_cast<void *>(reinterpret_cast<uptr>(Ptr) -
Chunk::getHeaderSize())); Chunk::getHeaderSize()));
@ -149,9 +147,6 @@ public:
initFlags(); initFlags();
reportUnrecognizedFlags(); reportUnrecognizedFlags();
RssChecker.init(scudo::getFlags()->soft_rss_limit_mb,
scudo::getFlags()->hard_rss_limit_mb);
// Store some flags locally. // Store some flags locally.
if (getFlags()->may_return_null) if (getFlags()->may_return_null)
Primary.Options.set(OptionBit::MayReturnNull); Primary.Options.set(OptionBit::MayReturnNull);
@ -251,12 +246,14 @@ public:
// - unlinking the local stats from the global ones (destroying the cache does // - unlinking the local stats from the global ones (destroying the cache does
// the last two items). // the last two items).
void commitBack(TSD<ThisT> *TSD) { void commitBack(TSD<ThisT> *TSD) {
TSD->assertLocked(/*BypassCheck=*/true);
Quarantine.drain(&TSD->getQuarantineCache(), Quarantine.drain(&TSD->getQuarantineCache(),
QuarantineCallback(*this, TSD->getCache())); QuarantineCallback(*this, TSD->getCache()));
TSD->getCache().destroy(&Stats); TSD->getCache().destroy(&Stats);
} }
void drainCache(TSD<ThisT> *TSD) { void drainCache(TSD<ThisT> *TSD) {
TSD->assertLocked(/*BypassCheck=*/true);
Quarantine.drainAndRecycle(&TSD->getQuarantineCache(), Quarantine.drainAndRecycle(&TSD->getQuarantineCache(),
QuarantineCallback(*this, TSD->getCache())); QuarantineCallback(*this, TSD->getCache()));
TSD->getCache().drain(); TSD->getCache().drain();
@ -299,7 +296,7 @@ public:
#endif #endif
} }
uptr computeOddEvenMaskForPointerMaybe(Options Options, uptr Ptr, uptr computeOddEvenMaskForPointerMaybe(const Options &Options, uptr Ptr,
uptr ClassId) { uptr ClassId) {
if (!Options.get(OptionBit::UseOddEvenTags)) if (!Options.get(OptionBit::UseOddEvenTags))
return 0; return 0;
@ -329,8 +326,6 @@ public:
#ifdef GWP_ASAN_HOOKS #ifdef GWP_ASAN_HOOKS
if (UNLIKELY(GuardedAlloc.shouldSample())) { if (UNLIKELY(GuardedAlloc.shouldSample())) {
if (void *Ptr = GuardedAlloc.allocate(Size, Alignment)) { if (void *Ptr = GuardedAlloc.allocate(Size, Alignment)) {
if (UNLIKELY(&__scudo_allocate_hook))
__scudo_allocate_hook(Ptr, Size);
Stats.lock(); Stats.lock();
Stats.add(StatAllocated, GuardedAllocSlotSize); Stats.add(StatAllocated, GuardedAllocSlotSize);
Stats.sub(StatFree, GuardedAllocSlotSize); Stats.sub(StatFree, GuardedAllocSlotSize);
@ -363,19 +358,6 @@ public:
} }
DCHECK_LE(Size, NeededSize); DCHECK_LE(Size, NeededSize);
switch (RssChecker.getRssLimitExceeded()) {
case RssLimitChecker::Neither:
break;
case RssLimitChecker::Soft:
if (Options.get(OptionBit::MayReturnNull))
return nullptr;
reportSoftRSSLimit(RssChecker.getSoftRssLimit());
break;
case RssLimitChecker::Hard:
reportHardRSSLimit(RssChecker.getHardRssLimit());
break;
}
void *Block = nullptr; void *Block = nullptr;
uptr ClassId = 0; uptr ClassId = 0;
uptr SecondaryBlockEnd = 0; uptr SecondaryBlockEnd = 0;
@ -384,11 +366,11 @@ public:
DCHECK_NE(ClassId, 0U); DCHECK_NE(ClassId, 0U);
bool UnlockRequired; bool UnlockRequired;
auto *TSD = TSDRegistry.getTSDAndLock(&UnlockRequired); auto *TSD = TSDRegistry.getTSDAndLock(&UnlockRequired);
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
Block = TSD->getCache().allocate(ClassId); Block = TSD->getCache().allocate(ClassId);
// If the allocation failed, the most likely reason with a 32-bit primary // If the allocation failed, retry in each successively larger class until
// is the region being full. In that event, retry in each successively // it fits. If it fails to fit in the largest class, fallback to the
// larger class until it fits. If it fails to fit in the largest class, // Secondary.
// fallback to the Secondary.
if (UNLIKELY(!Block)) { if (UNLIKELY(!Block)) {
while (ClassId < SizeClassMap::LargestClassId && !Block) while (ClassId < SizeClassMap::LargestClassId && !Block)
Block = TSD->getCache().allocate(++ClassId); Block = TSD->getCache().allocate(++ClassId);
@ -406,6 +388,7 @@ public:
if (UNLIKELY(!Block)) { if (UNLIKELY(!Block)) {
if (Options.get(OptionBit::MayReturnNull)) if (Options.get(OptionBit::MayReturnNull))
return nullptr; return nullptr;
printStats();
reportOutOfMemory(NeededSize); reportOutOfMemory(NeededSize);
} }
@ -535,14 +518,14 @@ public:
Chunk::SizeOrUnusedBytesMask; Chunk::SizeOrUnusedBytesMask;
Chunk::storeHeader(Cookie, Ptr, &Header); Chunk::storeHeader(Cookie, Ptr, &Header);
if (UNLIKELY(&__scudo_allocate_hook))
__scudo_allocate_hook(TaggedPtr, Size);
return TaggedPtr; return TaggedPtr;
} }
NOINLINE void deallocate(void *Ptr, Chunk::Origin Origin, uptr DeleteSize = 0, NOINLINE void deallocate(void *Ptr, Chunk::Origin Origin, uptr DeleteSize = 0,
UNUSED uptr Alignment = MinAlignment) { UNUSED uptr Alignment = MinAlignment) {
if (UNLIKELY(!Ptr))
return;
// For a deallocation, we only ensure minimal initialization, meaning thread // For a deallocation, we only ensure minimal initialization, meaning thread
// local data will be left uninitialized for now (when using ELF TLS). The // local data will be left uninitialized for now (when using ELF TLS). The
// fallback cache will be used instead. This is a workaround for a situation // fallback cache will be used instead. This is a workaround for a situation
@ -551,12 +534,6 @@ public:
// being destroyed properly. Any other heap operation will do a full init. // being destroyed properly. Any other heap operation will do a full init.
initThreadMaybe(/*MinimalInit=*/true); initThreadMaybe(/*MinimalInit=*/true);
if (UNLIKELY(&__scudo_deallocate_hook))
__scudo_deallocate_hook(Ptr);
if (UNLIKELY(!Ptr))
return;
#ifdef GWP_ASAN_HOOKS #ifdef GWP_ASAN_HOOKS
if (UNLIKELY(GuardedAlloc.pointerIsMine(Ptr))) { if (UNLIKELY(GuardedAlloc.pointerIsMine(Ptr))) {
GuardedAlloc.deallocate(Ptr); GuardedAlloc.deallocate(Ptr);
@ -635,47 +612,46 @@ public:
if (UNLIKELY(!isAligned(reinterpret_cast<uptr>(OldPtr), MinAlignment))) if (UNLIKELY(!isAligned(reinterpret_cast<uptr>(OldPtr), MinAlignment)))
reportMisalignedPointer(AllocatorAction::Reallocating, OldPtr); reportMisalignedPointer(AllocatorAction::Reallocating, OldPtr);
Chunk::UnpackedHeader OldHeader; Chunk::UnpackedHeader Header;
Chunk::loadHeader(Cookie, OldPtr, &OldHeader); Chunk::loadHeader(Cookie, OldPtr, &Header);
if (UNLIKELY(OldHeader.State != Chunk::State::Allocated)) if (UNLIKELY(Header.State != Chunk::State::Allocated))
reportInvalidChunkState(AllocatorAction::Reallocating, OldPtr); reportInvalidChunkState(AllocatorAction::Reallocating, OldPtr);
// Pointer has to be allocated with a malloc-type function. Some // Pointer has to be allocated with a malloc-type function. Some
// applications think that it is OK to realloc a memalign'ed pointer, which // applications think that it is OK to realloc a memalign'ed pointer, which
// will trigger this check. It really isn't. // will trigger this check. It really isn't.
if (Options.get(OptionBit::DeallocTypeMismatch)) { if (Options.get(OptionBit::DeallocTypeMismatch)) {
if (UNLIKELY(OldHeader.OriginOrWasZeroed != Chunk::Origin::Malloc)) if (UNLIKELY(Header.OriginOrWasZeroed != Chunk::Origin::Malloc))
reportDeallocTypeMismatch(AllocatorAction::Reallocating, OldPtr, reportDeallocTypeMismatch(AllocatorAction::Reallocating, OldPtr,
OldHeader.OriginOrWasZeroed, Header.OriginOrWasZeroed,
Chunk::Origin::Malloc); Chunk::Origin::Malloc);
} }
void *BlockBegin = getBlockBegin(OldTaggedPtr, &OldHeader); void *BlockBegin = getBlockBegin(OldTaggedPtr, &Header);
uptr BlockEnd; uptr BlockEnd;
uptr OldSize; uptr OldSize;
const uptr ClassId = OldHeader.ClassId; const uptr ClassId = Header.ClassId;
if (LIKELY(ClassId)) { if (LIKELY(ClassId)) {
BlockEnd = reinterpret_cast<uptr>(BlockBegin) + BlockEnd = reinterpret_cast<uptr>(BlockBegin) +
SizeClassMap::getSizeByClassId(ClassId); SizeClassMap::getSizeByClassId(ClassId);
OldSize = OldHeader.SizeOrUnusedBytes; OldSize = Header.SizeOrUnusedBytes;
} else { } else {
BlockEnd = SecondaryT::getBlockEnd(BlockBegin); BlockEnd = SecondaryT::getBlockEnd(BlockBegin);
OldSize = BlockEnd - (reinterpret_cast<uptr>(OldTaggedPtr) + OldSize = BlockEnd - (reinterpret_cast<uptr>(OldTaggedPtr) +
OldHeader.SizeOrUnusedBytes); Header.SizeOrUnusedBytes);
} }
// If the new chunk still fits in the previously allocated block (with a // If the new chunk still fits in the previously allocated block (with a
// reasonable delta), we just keep the old block, and update the chunk // reasonable delta), we just keep the old block, and update the chunk
// header to reflect the size change. // header to reflect the size change.
if (reinterpret_cast<uptr>(OldTaggedPtr) + NewSize <= BlockEnd) { if (reinterpret_cast<uptr>(OldTaggedPtr) + NewSize <= BlockEnd) {
if (NewSize > OldSize || (OldSize - NewSize) < getPageSizeCached()) { if (NewSize > OldSize || (OldSize - NewSize) < getPageSizeCached()) {
Chunk::UnpackedHeader NewHeader = OldHeader; Header.SizeOrUnusedBytes =
NewHeader.SizeOrUnusedBytes =
(ClassId ? NewSize (ClassId ? NewSize
: BlockEnd - : BlockEnd -
(reinterpret_cast<uptr>(OldTaggedPtr) + NewSize)) & (reinterpret_cast<uptr>(OldTaggedPtr) + NewSize)) &
Chunk::SizeOrUnusedBytesMask; Chunk::SizeOrUnusedBytesMask;
Chunk::compareExchangeHeader(Cookie, OldPtr, &NewHeader, &OldHeader); Chunk::storeHeader(Cookie, OldPtr, &Header);
if (UNLIKELY(useMemoryTagging<Config>(Options))) { if (UNLIKELY(useMemoryTagging<Config>(Options))) {
if (ClassId) { if (ClassId) {
resizeTaggedChunk(reinterpret_cast<uptr>(OldTaggedPtr) + OldSize, resizeTaggedChunk(reinterpret_cast<uptr>(OldTaggedPtr) + OldSize,
@ -697,9 +673,7 @@ public:
void *NewPtr = allocate(NewSize, Chunk::Origin::Malloc, Alignment); void *NewPtr = allocate(NewSize, Chunk::Origin::Malloc, Alignment);
if (LIKELY(NewPtr)) { if (LIKELY(NewPtr)) {
memcpy(NewPtr, OldTaggedPtr, Min(NewSize, OldSize)); memcpy(NewPtr, OldTaggedPtr, Min(NewSize, OldSize));
if (UNLIKELY(&__scudo_deallocate_hook)) quarantineOrDeallocateChunk(Options, OldTaggedPtr, &Header, OldSize);
__scudo_deallocate_hook(OldTaggedPtr);
quarantineOrDeallocateChunk(Options, OldTaggedPtr, &OldHeader, OldSize);
} }
return NewPtr; return NewPtr;
} }
@ -754,6 +728,13 @@ public:
Str.output(); Str.output();
} }
void printFragmentationInfo() {
ScopedString Str;
Primary.getFragmentationInfo(&Str);
// Secondary allocator dumps the fragmentation data in getStats().
Str.output();
}
void releaseToOS(ReleaseToOS ReleaseType) { void releaseToOS(ReleaseToOS ReleaseType) {
initThreadMaybe(); initThreadMaybe();
if (ReleaseType == ReleaseToOS::ForceAll) if (ReleaseType == ReleaseToOS::ForceAll)
@ -847,10 +828,15 @@ public:
// for it, which then forces realloc to copy the usable size of a chunk as // for it, which then forces realloc to copy the usable size of a chunk as
// opposed to its actual size. // opposed to its actual size.
uptr getUsableSize(const void *Ptr) { uptr getUsableSize(const void *Ptr) {
initThreadMaybe();
if (UNLIKELY(!Ptr)) if (UNLIKELY(!Ptr))
return 0; return 0;
return getAllocSize(Ptr);
}
uptr getAllocSize(const void *Ptr) {
initThreadMaybe();
#ifdef GWP_ASAN_HOOKS #ifdef GWP_ASAN_HOOKS
if (UNLIKELY(GuardedAlloc.pointerIsMine(Ptr))) if (UNLIKELY(GuardedAlloc.pointerIsMine(Ptr)))
return GuardedAlloc.getSize(Ptr); return GuardedAlloc.getSize(Ptr);
@ -859,9 +845,11 @@ public:
Ptr = getHeaderTaggedPointer(const_cast<void *>(Ptr)); Ptr = getHeaderTaggedPointer(const_cast<void *>(Ptr));
Chunk::UnpackedHeader Header; Chunk::UnpackedHeader Header;
Chunk::loadHeader(Cookie, Ptr, &Header); Chunk::loadHeader(Cookie, Ptr, &Header);
// Getting the usable size of a chunk only makes sense if it's allocated.
// Getting the alloc size of a chunk only makes sense if it's allocated.
if (UNLIKELY(Header.State != Chunk::State::Allocated)) if (UNLIKELY(Header.State != Chunk::State::Allocated))
reportInvalidChunkState(AllocatorAction::Sizing, const_cast<void *>(Ptr)); reportInvalidChunkState(AllocatorAction::Sizing, const_cast<void *>(Ptr));
return getSize(Ptr, &Header); return getSize(Ptr, &Header);
} }
@ -887,13 +875,6 @@ public:
Header.State == Chunk::State::Allocated; Header.State == Chunk::State::Allocated;
} }
void setRssLimitsTestOnly(int SoftRssLimitMb, int HardRssLimitMb,
bool MayReturnNull) {
RssChecker.init(SoftRssLimitMb, HardRssLimitMb);
if (MayReturnNull)
Primary.Options.set(OptionBit::MayReturnNull);
}
bool useMemoryTaggingTestOnly() const { bool useMemoryTaggingTestOnly() const {
return useMemoryTagging<Config>(Primary.Options.load()); return useMemoryTagging<Config>(Primary.Options.load());
} }
@ -913,7 +894,7 @@ public:
void setTrackAllocationStacks(bool Track) { void setTrackAllocationStacks(bool Track) {
initThreadMaybe(); initThreadMaybe();
if (getFlags()->allocation_ring_buffer_size == 0) { if (getFlags()->allocation_ring_buffer_size <= 0) {
DCHECK(!Primary.Options.load().get(OptionBit::TrackAllocationStacks)); DCHECK(!Primary.Options.load().get(OptionBit::TrackAllocationStacks));
return; return;
} }
@ -955,21 +936,7 @@ public:
uptr getRingBufferSize() { uptr getRingBufferSize() {
initThreadMaybe(); initThreadMaybe();
auto *RingBuffer = getRingBuffer(); return RingBufferElements ? ringBufferSizeInBytes(RingBufferElements) : 0;
return RingBuffer ? ringBufferSizeInBytes(RingBuffer->Size) : 0;
}
static bool setRingBufferSizeForBuffer(char *Buffer, size_t Size) {
// Need at least one entry.
if (Size < sizeof(AllocationRingBuffer) +
sizeof(typename AllocationRingBuffer::Entry)) {
return false;
}
AllocationRingBuffer *RingBuffer =
reinterpret_cast<AllocationRingBuffer *>(Buffer);
RingBuffer->Size = (Size - sizeof(AllocationRingBuffer)) /
sizeof(typename AllocationRingBuffer::Entry);
return true;
} }
static const uptr MaxTraceSize = 64; static const uptr MaxTraceSize = 64;
@ -986,8 +953,9 @@ public:
static void getErrorInfo(struct scudo_error_info *ErrorInfo, static void getErrorInfo(struct scudo_error_info *ErrorInfo,
uintptr_t FaultAddr, const char *DepotPtr, uintptr_t FaultAddr, const char *DepotPtr,
const char *RegionInfoPtr, const char *RingBufferPtr, const char *RegionInfoPtr, const char *RingBufferPtr,
const char *Memory, const char *MemoryTags, size_t RingBufferSize, const char *Memory,
uintptr_t MemoryAddr, size_t MemorySize) { const char *MemoryTags, uintptr_t MemoryAddr,
size_t MemorySize) {
*ErrorInfo = {}; *ErrorInfo = {};
if (!allocatorSupportsMemoryTagging<Config>() || if (!allocatorSupportsMemoryTagging<Config>() ||
MemoryAddr + MemorySize < MemoryAddr) MemoryAddr + MemorySize < MemoryAddr)
@ -1006,7 +974,7 @@ public:
// Check the ring buffer. For primary allocations this will only find UAF; // Check the ring buffer. For primary allocations this will only find UAF;
// for secondary allocations we can find either UAF or OOB. // for secondary allocations we can find either UAF or OOB.
getRingBufferErrorInfo(ErrorInfo, NextErrorReport, FaultAddr, Depot, getRingBufferErrorInfo(ErrorInfo, NextErrorReport, FaultAddr, Depot,
RingBufferPtr); RingBufferPtr, RingBufferSize);
// Check for OOB in the 28 blocks surrounding the 3 we checked earlier. // Check for OOB in the 28 blocks surrounding the 3 we checked earlier.
// Beyond that we are likely to hit false positives. // Beyond that we are likely to hit false positives.
@ -1053,7 +1021,6 @@ private:
QuarantineT Quarantine; QuarantineT Quarantine;
TSDRegistryT TSDRegistry; TSDRegistryT TSDRegistry;
pthread_once_t PostInitNonce = PTHREAD_ONCE_INIT; pthread_once_t PostInitNonce = PTHREAD_ONCE_INIT;
RssLimitChecker RssChecker;
#ifdef GWP_ASAN_HOOKS #ifdef GWP_ASAN_HOOKS
gwp_asan::GuardedPoolAllocator GuardedAlloc; gwp_asan::GuardedPoolAllocator GuardedAlloc;
@ -1073,13 +1040,14 @@ private:
}; };
atomic_uptr Pos; atomic_uptr Pos;
u32 Size;
// An array of Size (at least one) elements of type Entry is immediately // An array of Size (at least one) elements of type Entry is immediately
// following to this struct. // following to this struct.
}; };
// Pointer to memory mapped area starting with AllocationRingBuffer struct, // Pointer to memory mapped area starting with AllocationRingBuffer struct,
// and immediately followed by Size elements of type Entry. // and immediately followed by Size elements of type Entry.
char *RawRingBuffer = {}; char *RawRingBuffer = {};
u32 RingBufferElements = 0;
MemMapT RawRingBufferMap;
// The following might get optimized out by the compiler. // The following might get optimized out by the compiler.
NOINLINE void performSanityChecks() { NOINLINE void performSanityChecks() {
@ -1134,35 +1102,34 @@ private:
reinterpret_cast<uptr>(Ptr) - SizeOrUnusedBytes; reinterpret_cast<uptr>(Ptr) - SizeOrUnusedBytes;
} }
void quarantineOrDeallocateChunk(Options Options, void *TaggedPtr, void quarantineOrDeallocateChunk(const Options &Options, void *TaggedPtr,
Chunk::UnpackedHeader *Header, Chunk::UnpackedHeader *Header,
uptr Size) NO_THREAD_SAFETY_ANALYSIS { uptr Size) NO_THREAD_SAFETY_ANALYSIS {
void *Ptr = getHeaderTaggedPointer(TaggedPtr); void *Ptr = getHeaderTaggedPointer(TaggedPtr);
Chunk::UnpackedHeader NewHeader = *Header;
// If the quarantine is disabled, the actual size of a chunk is 0 or larger // If the quarantine is disabled, the actual size of a chunk is 0 or larger
// than the maximum allowed, we return a chunk directly to the backend. // than the maximum allowed, we return a chunk directly to the backend.
// This purposefully underflows for Size == 0. // This purposefully underflows for Size == 0.
const bool BypassQuarantine = !Quarantine.getCacheSize() || const bool BypassQuarantine = !Quarantine.getCacheSize() ||
((Size - 1) >= QuarantineMaxChunkSize) || ((Size - 1) >= QuarantineMaxChunkSize) ||
!NewHeader.ClassId; !Header->ClassId;
if (BypassQuarantine) if (BypassQuarantine)
NewHeader.State = Chunk::State::Available; Header->State = Chunk::State::Available;
else else
NewHeader.State = Chunk::State::Quarantined; Header->State = Chunk::State::Quarantined;
NewHeader.OriginOrWasZeroed = useMemoryTagging<Config>(Options) && Header->OriginOrWasZeroed = useMemoryTagging<Config>(Options) &&
NewHeader.ClassId && Header->ClassId &&
!TSDRegistry.getDisableMemInit(); !TSDRegistry.getDisableMemInit();
Chunk::compareExchangeHeader(Cookie, Ptr, &NewHeader, Header); Chunk::storeHeader(Cookie, Ptr, Header);
if (UNLIKELY(useMemoryTagging<Config>(Options))) { if (UNLIKELY(useMemoryTagging<Config>(Options))) {
u8 PrevTag = extractTag(reinterpret_cast<uptr>(TaggedPtr)); u8 PrevTag = extractTag(reinterpret_cast<uptr>(TaggedPtr));
storeDeallocationStackMaybe(Options, Ptr, PrevTag, Size); storeDeallocationStackMaybe(Options, Ptr, PrevTag, Size);
if (NewHeader.ClassId) { if (Header->ClassId) {
if (!TSDRegistry.getDisableMemInit()) { if (!TSDRegistry.getDisableMemInit()) {
uptr TaggedBegin, TaggedEnd; uptr TaggedBegin, TaggedEnd;
const uptr OddEvenMask = computeOddEvenMaskForPointerMaybe( const uptr OddEvenMask = computeOddEvenMaskForPointerMaybe(
Options, reinterpret_cast<uptr>(getBlockBegin(Ptr, &NewHeader)), Options, reinterpret_cast<uptr>(getBlockBegin(Ptr, Header)),
NewHeader.ClassId); Header->ClassId);
// Exclude the previous tag so that immediate use after free is // Exclude the previous tag so that immediate use after free is
// detected 100% of the time. // detected 100% of the time.
setRandomTag(Ptr, Size, OddEvenMask | (1UL << PrevTag), &TaggedBegin, setRandomTag(Ptr, Size, OddEvenMask | (1UL << PrevTag), &TaggedBegin,
@ -1173,11 +1140,12 @@ private:
if (BypassQuarantine) { if (BypassQuarantine) {
if (allocatorSupportsMemoryTagging<Config>()) if (allocatorSupportsMemoryTagging<Config>())
Ptr = untagPointer(Ptr); Ptr = untagPointer(Ptr);
void *BlockBegin = getBlockBegin(Ptr, &NewHeader); void *BlockBegin = getBlockBegin(Ptr, Header);
const uptr ClassId = NewHeader.ClassId; const uptr ClassId = Header->ClassId;
if (LIKELY(ClassId)) { if (LIKELY(ClassId)) {
bool UnlockRequired; bool UnlockRequired;
auto *TSD = TSDRegistry.getTSDAndLock(&UnlockRequired); auto *TSD = TSDRegistry.getTSDAndLock(&UnlockRequired);
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
const bool CacheDrained = const bool CacheDrained =
TSD->getCache().deallocate(ClassId, BlockBegin); TSD->getCache().deallocate(ClassId, BlockBegin);
if (UnlockRequired) if (UnlockRequired)
@ -1197,6 +1165,7 @@ private:
} else { } else {
bool UnlockRequired; bool UnlockRequired;
auto *TSD = TSDRegistry.getTSDAndLock(&UnlockRequired); auto *TSD = TSDRegistry.getTSDAndLock(&UnlockRequired);
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
Quarantine.put(&TSD->getQuarantineCache(), Quarantine.put(&TSD->getQuarantineCache(),
QuarantineCallback(*this, TSD->getCache()), Ptr, Size); QuarantineCallback(*this, TSD->getCache()), Ptr, Size);
if (UnlockRequired) if (UnlockRequired)
@ -1273,7 +1242,7 @@ private:
storeEndMarker(RoundNewPtr, NewSize, BlockEnd); storeEndMarker(RoundNewPtr, NewSize, BlockEnd);
} }
void storePrimaryAllocationStackMaybe(Options Options, void *Ptr) { void storePrimaryAllocationStackMaybe(const Options &Options, void *Ptr) {
if (!UNLIKELY(Options.get(OptionBit::TrackAllocationStacks))) if (!UNLIKELY(Options.get(OptionBit::TrackAllocationStacks)))
return; return;
auto *Ptr32 = reinterpret_cast<u32 *>(Ptr); auto *Ptr32 = reinterpret_cast<u32 *>(Ptr);
@ -1286,7 +1255,7 @@ private:
u32 DeallocationTid) { u32 DeallocationTid) {
uptr Pos = atomic_fetch_add(&getRingBuffer()->Pos, 1, memory_order_relaxed); uptr Pos = atomic_fetch_add(&getRingBuffer()->Pos, 1, memory_order_relaxed);
typename AllocationRingBuffer::Entry *Entry = typename AllocationRingBuffer::Entry *Entry =
getRingBufferEntry(RawRingBuffer, Pos % getRingBuffer()->Size); getRingBufferEntry(RawRingBuffer, Pos % RingBufferElements);
// First invalidate our entry so that we don't attempt to interpret a // First invalidate our entry so that we don't attempt to interpret a
// partially written state in getSecondaryErrorInfo(). The fences below // partially written state in getSecondaryErrorInfo(). The fences below
@ -1305,7 +1274,7 @@ private:
atomic_store_relaxed(&Entry->Ptr, reinterpret_cast<uptr>(Ptr)); atomic_store_relaxed(&Entry->Ptr, reinterpret_cast<uptr>(Ptr));
} }
void storeSecondaryAllocationStackMaybe(Options Options, void *Ptr, void storeSecondaryAllocationStackMaybe(const Options &Options, void *Ptr,
uptr Size) { uptr Size) {
if (!UNLIKELY(Options.get(OptionBit::TrackAllocationStacks))) if (!UNLIKELY(Options.get(OptionBit::TrackAllocationStacks)))
return; return;
@ -1320,8 +1289,8 @@ private:
storeRingBufferEntry(untagPointer(Ptr), Trace, Tid, Size, 0, 0); storeRingBufferEntry(untagPointer(Ptr), Trace, Tid, Size, 0, 0);
} }
void storeDeallocationStackMaybe(Options Options, void *Ptr, u8 PrevTag, void storeDeallocationStackMaybe(const Options &Options, void *Ptr,
uptr Size) { u8 PrevTag, uptr Size) {
if (!UNLIKELY(Options.get(OptionBit::TrackAllocationStacks))) if (!UNLIKELY(Options.get(OptionBit::TrackAllocationStacks)))
return; return;
@ -1427,17 +1396,19 @@ private:
size_t &NextErrorReport, size_t &NextErrorReport,
uintptr_t FaultAddr, uintptr_t FaultAddr,
const StackDepot *Depot, const StackDepot *Depot,
const char *RingBufferPtr) { const char *RingBufferPtr,
size_t RingBufferSize) {
auto *RingBuffer = auto *RingBuffer =
reinterpret_cast<const AllocationRingBuffer *>(RingBufferPtr); reinterpret_cast<const AllocationRingBuffer *>(RingBufferPtr);
if (!RingBuffer || RingBuffer->Size == 0) size_t RingBufferElements = ringBufferElementsFromBytes(RingBufferSize);
if (!RingBuffer || RingBufferElements == 0)
return; return;
uptr Pos = atomic_load_relaxed(&RingBuffer->Pos); uptr Pos = atomic_load_relaxed(&RingBuffer->Pos);
for (uptr I = Pos - 1; for (uptr I = Pos - 1; I != Pos - 1 - RingBufferElements &&
I != Pos - 1 - RingBuffer->Size && NextErrorReport != NumErrorReports; NextErrorReport != NumErrorReports;
--I) { --I) {
auto *Entry = getRingBufferEntry(RingBufferPtr, I % RingBuffer->Size); auto *Entry = getRingBufferEntry(RingBufferPtr, I % RingBufferElements);
uptr EntryPtr = atomic_load_relaxed(&Entry->Ptr); uptr EntryPtr = atomic_load_relaxed(&Entry->Ptr);
if (!EntryPtr) if (!EntryPtr)
continue; continue;
@ -1516,17 +1487,19 @@ private:
} }
void mapAndInitializeRingBuffer() { void mapAndInitializeRingBuffer() {
if (getFlags()->allocation_ring_buffer_size <= 0)
return;
u32 AllocationRingBufferSize = u32 AllocationRingBufferSize =
static_cast<u32>(getFlags()->allocation_ring_buffer_size); static_cast<u32>(getFlags()->allocation_ring_buffer_size);
if (AllocationRingBufferSize < 1) MemMapT MemMap;
return; MemMap.map(
RawRingBuffer = static_cast<char *>( /*Addr=*/0U,
map(/*Addr=*/nullptr, roundUp(ringBufferSizeInBytes(AllocationRingBufferSize),
roundUp(ringBufferSizeInBytes(AllocationRingBufferSize), getPageSizeCached()),
getPageSizeCached()), "scudo:ring_buffer");
"AllocatorRingBuffer")); RawRingBuffer = reinterpret_cast<char *>(MemMap.getBase());
auto *RingBuffer = reinterpret_cast<AllocationRingBuffer *>(RawRingBuffer); RawRingBufferMap = MemMap;
RingBuffer->Size = AllocationRingBufferSize; RingBufferElements = AllocationRingBufferSize;
static_assert(sizeof(AllocationRingBuffer) % static_assert(sizeof(AllocationRingBuffer) %
alignof(typename AllocationRingBuffer::Entry) == alignof(typename AllocationRingBuffer::Entry) ==
0, 0,
@ -1534,14 +1507,25 @@ private:
} }
void unmapRingBuffer() { void unmapRingBuffer() {
unmap(RawRingBuffer, roundUp(getRingBufferSize(), getPageSizeCached())); auto *RingBuffer = getRingBuffer();
if (RingBuffer != nullptr) {
RawRingBufferMap.unmap(RawRingBufferMap.getBase(),
RawRingBufferMap.getCapacity());
}
RawRingBuffer = nullptr; RawRingBuffer = nullptr;
} }
static constexpr size_t ringBufferSizeInBytes(u32 AllocationRingBufferSize) { static constexpr size_t ringBufferSizeInBytes(u32 RingBufferElements) {
return sizeof(AllocationRingBuffer) + return sizeof(AllocationRingBuffer) +
AllocationRingBufferSize * RingBufferElements * sizeof(typename AllocationRingBuffer::Entry);
sizeof(typename AllocationRingBuffer::Entry); }
static constexpr size_t ringBufferElementsFromBytes(size_t Bytes) {
if (Bytes < sizeof(AllocationRingBuffer)) {
return 0;
}
return (Bytes - sizeof(AllocationRingBuffer)) /
sizeof(typename AllocationRingBuffer::Entry);
} }
inline AllocationRingBuffer *getRingBuffer() { inline AllocationRingBuffer *getRingBuffer() {

View file

@ -21,22 +21,4 @@ uptr getPageSizeSlow() {
return PageSizeCached; return PageSizeCached;
} }
// Fatal internal map() or unmap() error (potentially OOM related).
void NORETURN dieOnMapUnmapError(uptr SizeIfOOM) {
char Error[128] = "Scudo ERROR: internal map or unmap failure\n";
if (SizeIfOOM) {
formatString(
Error, sizeof(Error),
"Scudo ERROR: internal map failure (NO MEMORY) requesting %zuKB\n",
SizeIfOOM >> 10);
}
outputRaw(Error);
setAbortMessage(Error);
die();
}
#if !SCUDO_LINUX
uptr GetRSS() { return 0; }
#endif
} // namespace scudo } // namespace scudo

View file

@ -17,6 +17,7 @@
#include <stddef.h> #include <stddef.h>
#include <string.h> #include <string.h>
#include <unistd.h>
namespace scudo { namespace scudo {
@ -111,19 +112,19 @@ template <typename T> inline void shuffle(T *A, u32 N, u32 *RandState) {
*RandState = State; *RandState = State;
} }
// Hardware specific inlinable functions. inline void computePercentage(uptr Numerator, uptr Denominator, uptr *Integral,
uptr *Fractional) {
constexpr uptr Digits = 100;
if (Denominator == 0) {
*Integral = 100;
*Fractional = 0;
return;
}
inline void yieldProcessor(UNUSED u8 Count) { *Integral = Numerator * Digits / Denominator;
#if defined(__i386__) || defined(__x86_64__) *Fractional =
__asm__ __volatile__("" ::: "memory"); (((Numerator * Digits) % Denominator) * Digits + Denominator / 2) /
for (u8 I = 0; I < Count; I++) Denominator;
__asm__ __volatile__("pause");
#elif defined(__aarch64__) || defined(__arm__)
__asm__ __volatile__("" ::: "memory");
for (u8 I = 0; I < Count; I++)
__asm__ __volatile__("yield");
#endif
__asm__ __volatile__("" ::: "memory");
} }
// Platform specific functions. // Platform specific functions.
@ -131,9 +132,10 @@ inline void yieldProcessor(UNUSED u8 Count) {
extern uptr PageSizeCached; extern uptr PageSizeCached;
uptr getPageSizeSlow(); uptr getPageSizeSlow();
inline uptr getPageSizeCached() { inline uptr getPageSizeCached() {
// Bionic uses a hardcoded value. #if SCUDO_ANDROID && defined(PAGE_SIZE)
if (SCUDO_ANDROID) // Most Android builds have a build-time constant page size.
return 4096U; return PAGE_SIZE;
#endif
if (LIKELY(PageSizeCached)) if (LIKELY(PageSizeCached))
return PageSizeCached; return PageSizeCached;
return getPageSizeSlow(); return getPageSizeSlow();
@ -144,8 +146,6 @@ u32 getNumberOfCPUs();
const char *getEnv(const char *Name); const char *getEnv(const char *Name);
uptr GetRSS();
u64 getMonotonicTime(); u64 getMonotonicTime();
// Gets the time faster but with less accuracy. Can call getMonotonicTime // Gets the time faster but with less accuracy. Can call getMonotonicTime
// if no fast version is available. // if no fast version is available.
@ -190,10 +190,6 @@ void setMemoryPermission(uptr Addr, uptr Size, uptr Flags,
void releasePagesToOS(uptr BaseAddress, uptr Offset, uptr Size, void releasePagesToOS(uptr BaseAddress, uptr Offset, uptr Size,
MapPlatformData *Data = nullptr); MapPlatformData *Data = nullptr);
// Internal map & unmap fatal error. This must not call map(). SizeIfOOM shall
// hold the requested size on an out-of-memory error, 0 otherwise.
void NORETURN dieOnMapUnmapError(uptr SizeIfOOM = 0);
// Logging related functions. // Logging related functions.
void setAbortMessage(const char *Message); void setAbortMessage(const char *Message);

View file

@ -0,0 +1,60 @@
//===-- condition_variable.h ------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef SCUDO_CONDITION_VARIABLE_H_
#define SCUDO_CONDITION_VARIABLE_H_
#include "condition_variable_base.h"
#include "common.h"
#include "platform.h"
#include "condition_variable_linux.h"
namespace scudo {
// A default implementation of default condition variable. It doesn't do a real
// `wait`, instead it spins a short amount of time only.
class ConditionVariableDummy
: public ConditionVariableBase<ConditionVariableDummy> {
public:
void notifyAllImpl(UNUSED HybridMutex &M) REQUIRES(M) {}
void waitImpl(UNUSED HybridMutex &M) REQUIRES(M) {
M.unlock();
constexpr u32 SpinTimes = 64;
volatile u32 V = 0;
for (u32 I = 0; I < SpinTimes; ++I) {
u32 Tmp = V + 1;
V = Tmp;
}
M.lock();
}
};
template <typename Config, typename = const bool>
struct ConditionVariableState {
static constexpr bool enabled() { return false; }
// This is only used for compilation purpose so that we won't end up having
// many conditional compilations. If you want to use `ConditionVariableDummy`,
// define `ConditionVariableT` in your allocator configuration. See
// allocator_config.h for more details.
using ConditionVariableT = ConditionVariableDummy;
};
template <typename Config>
struct ConditionVariableState<Config, decltype(Config::UseConditionVariable)> {
static constexpr bool enabled() { return Config::UseConditionVariable; }
using ConditionVariableT = typename Config::ConditionVariableT;
};
} // namespace scudo
#endif // SCUDO_CONDITION_VARIABLE_H_

View file

@ -0,0 +1,56 @@
//===-- condition_variable_base.h -------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef SCUDO_CONDITION_VARIABLE_BASE_H_
#define SCUDO_CONDITION_VARIABLE_BASE_H_
#include "mutex.h"
#include "thread_annotations.h"
namespace scudo {
template <typename Derived> class ConditionVariableBase {
public:
constexpr ConditionVariableBase() = default;
void bindTestOnly(HybridMutex &Mutex) {
#if SCUDO_DEBUG
boundMutex = &Mutex;
#else
(void)Mutex;
#endif
}
void notifyAll(HybridMutex &M) REQUIRES(M) {
#if SCUDO_DEBUG
CHECK_EQ(&M, boundMutex);
#endif
getDerived()->notifyAllImpl(M);
}
void wait(HybridMutex &M) REQUIRES(M) {
#if SCUDO_DEBUG
CHECK_EQ(&M, boundMutex);
#endif
getDerived()->waitImpl(M);
}
protected:
Derived *getDerived() { return static_cast<Derived *>(this); }
#if SCUDO_DEBUG
// Because thread-safety analysis doesn't support pointer aliasing, we are not
// able to mark the proper annotations without false positive. Instead, we
// pass the lock and do the same-lock check separately.
HybridMutex *boundMutex = nullptr;
#endif
};
} // namespace scudo
#endif // SCUDO_CONDITION_VARIABLE_BASE_H_

View file

@ -0,0 +1,52 @@
//===-- condition_variable_linux.cpp ----------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "platform.h"
#if SCUDO_LINUX
#include "condition_variable_linux.h"
#include "atomic_helpers.h"
#include <limits.h>
#include <linux/futex.h>
#include <sys/syscall.h>
#include <unistd.h>
namespace scudo {
void ConditionVariableLinux::notifyAllImpl(UNUSED HybridMutex &M) {
const u32 V = atomic_load_relaxed(&Counter);
atomic_store_relaxed(&Counter, V + 1);
// TODO(chiahungduan): Move the waiters from the futex waiting queue
// `Counter` to futex waiting queue `M` so that the awoken threads won't be
// blocked again due to locked `M` by current thread.
if (LastNotifyAll != V) {
syscall(SYS_futex, reinterpret_cast<uptr>(&Counter), FUTEX_WAKE_PRIVATE,
INT_MAX, nullptr, nullptr, 0);
}
LastNotifyAll = V + 1;
}
void ConditionVariableLinux::waitImpl(HybridMutex &M) {
const u32 V = atomic_load_relaxed(&Counter) + 1;
atomic_store_relaxed(&Counter, V);
// TODO: Use ScopedUnlock when it's supported.
M.unlock();
syscall(SYS_futex, reinterpret_cast<uptr>(&Counter), FUTEX_WAIT_PRIVATE, V,
nullptr, nullptr, 0);
M.lock();
}
} // namespace scudo
#endif // SCUDO_LINUX

View file

@ -0,0 +1,38 @@
//===-- condition_variable_linux.h ------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef SCUDO_CONDITION_VARIABLE_LINUX_H_
#define SCUDO_CONDITION_VARIABLE_LINUX_H_
#include "platform.h"
#if SCUDO_LINUX
#include "atomic_helpers.h"
#include "condition_variable_base.h"
#include "thread_annotations.h"
namespace scudo {
class ConditionVariableLinux
: public ConditionVariableBase<ConditionVariableLinux> {
public:
void notifyAllImpl(HybridMutex &M) REQUIRES(M);
void waitImpl(HybridMutex &M) REQUIRES(M);
private:
u32 LastNotifyAll = 0;
atomic_u32 Counter = {};
};
} // namespace scudo
#endif // SCUDO_LINUX
#endif // SCUDO_CONDITION_VARIABLE_LINUX_H_

View file

@ -68,6 +68,9 @@ void initFlags() {
Parser.parseString(getCompileDefinitionScudoDefaultOptions()); Parser.parseString(getCompileDefinitionScudoDefaultOptions());
Parser.parseString(getScudoDefaultOptions()); Parser.parseString(getScudoDefaultOptions());
Parser.parseString(getEnv("SCUDO_OPTIONS")); Parser.parseString(getEnv("SCUDO_OPTIONS"));
if (const char *V = getEnv("SCUDO_ALLOCATION_RING_BUFFER_SIZE")) {
Parser.parseStringPair("allocation_ring_buffer_size", V);
}
} }
} // namespace scudo } // namespace scudo

View file

@ -46,14 +46,6 @@ SCUDO_FLAG(int, release_to_os_interval_ms, SCUDO_ANDROID ? INT32_MIN : 5000,
"Interval (in milliseconds) at which to attempt release of unused " "Interval (in milliseconds) at which to attempt release of unused "
"memory to the OS. Negative values disable the feature.") "memory to the OS. Negative values disable the feature.")
SCUDO_FLAG(int, hard_rss_limit_mb, 0,
"Hard RSS Limit in Mb. If non-zero, once the limit is achieved, "
"abort the process")
SCUDO_FLAG(int, soft_rss_limit_mb, 0,
"Soft RSS Limit in Mb. If non-zero, once the limit is reached, all "
"subsequent calls will fail or return NULL until the RSS goes below "
"the soft limit")
SCUDO_FLAG(int, allocation_ring_buffer_size, 32768, SCUDO_FLAG(int, allocation_ring_buffer_size, 32768,
"Entries to keep in the allocation ring buffer for scudo.") "Entries to keep in the allocation ring buffer for scudo. "
"Values less or equal to zero disable the buffer.")

View file

@ -10,6 +10,8 @@
#include "common.h" #include "common.h"
#include "report.h" #include "report.h"
#include <errno.h>
#include <limits.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -80,7 +82,7 @@ void FlagParser::parseFlag() {
++Pos; ++Pos;
Value = Buffer + ValueStart; Value = Buffer + ValueStart;
} }
if (!runHandler(Name, Value)) if (!runHandler(Name, Value, '='))
reportError("flag parsing failed."); reportError("flag parsing failed.");
} }
@ -122,10 +124,16 @@ inline bool parseBool(const char *Value, bool *b) {
return false; return false;
} }
bool FlagParser::runHandler(const char *Name, const char *Value) { void FlagParser::parseStringPair(const char *Name, const char *Value) {
if (!runHandler(Name, Value, '\0'))
reportError("flag parsing failed.");
}
bool FlagParser::runHandler(const char *Name, const char *Value,
const char Sep) {
for (u32 I = 0; I < NumberOfFlags; ++I) { for (u32 I = 0; I < NumberOfFlags; ++I) {
const uptr Len = strlen(Flags[I].Name); const uptr Len = strlen(Flags[I].Name);
if (strncmp(Name, Flags[I].Name, Len) != 0 || Name[Len] != '=') if (strncmp(Name, Flags[I].Name, Len) != 0 || Name[Len] != Sep)
continue; continue;
bool Ok = false; bool Ok = false;
switch (Flags[I].Type) { switch (Flags[I].Type) {
@ -136,12 +144,18 @@ bool FlagParser::runHandler(const char *Name, const char *Value) {
break; break;
case FlagType::FT_int: case FlagType::FT_int:
char *ValueEnd; char *ValueEnd;
*reinterpret_cast<int *>(Flags[I].Var) = errno = 0;
static_cast<int>(strtol(Value, &ValueEnd, 10)); long V = strtol(Value, &ValueEnd, 10);
Ok = if (errno != 0 || // strtol failed (over or underflow)
*ValueEnd == '"' || *ValueEnd == '\'' || isSeparatorOrNull(*ValueEnd); V > INT_MAX || V < INT_MIN || // overflows integer
if (!Ok) // contains unexpected characters
(*ValueEnd != '"' && *ValueEnd != '\'' &&
!isSeparatorOrNull(*ValueEnd))) {
reportInvalidFlag("int", Value); reportInvalidFlag("int", Value);
break;
}
*reinterpret_cast<int *>(Flags[I].Var) = static_cast<int>(V);
Ok = true;
break; break;
} }
return Ok; return Ok;

View file

@ -27,6 +27,7 @@ public:
void *Var); void *Var);
void parseString(const char *S); void parseString(const char *S);
void printFlagDescriptions(); void printFlagDescriptions();
void parseStringPair(const char *Name, const char *Value);
private: private:
static const u32 MaxFlags = 20; static const u32 MaxFlags = 20;
@ -45,7 +46,7 @@ private:
void skipWhitespace(); void skipWhitespace();
void parseFlags(); void parseFlags();
void parseFlag(); void parseFlag();
bool runHandler(const char *Name, const char *Value); bool runHandler(const char *Name, const char *Value, char Sep);
}; };
void reportUnrecognizedFlags(); void reportUnrecognizedFlags();

View file

@ -46,14 +46,11 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t *Data, size_t Size) {
} }
std::string RingBufferBytes = FDP.ConsumeRemainingBytesAsString(); std::string RingBufferBytes = FDP.ConsumeRemainingBytesAsString();
// RingBuffer is too short.
if (!AllocatorT::setRingBufferSizeForBuffer(RingBufferBytes.data(),
RingBufferBytes.size()))
return 0;
scudo_error_info ErrorInfo; scudo_error_info ErrorInfo;
AllocatorT::getErrorInfo(&ErrorInfo, FaultAddr, StackDepot.data(), AllocatorT::getErrorInfo(&ErrorInfo, FaultAddr, StackDepot.data(),
RegionInfo.data(), RingBufferBytes.data(), Memory, RegionInfo.data(), RingBufferBytes.data(),
MemoryTags, MemoryAddr, MemorySize); RingBufferBytes.size(), Memory, MemoryTags,
MemoryAddr, MemorySize);
return 0; return 0;
} }

View file

@ -17,10 +17,22 @@ extern "C" {
__attribute__((weak)) const char *__scudo_default_options(void); __attribute__((weak)) const char *__scudo_default_options(void);
// Post-allocation & pre-deallocation hooks. // Post-allocation & pre-deallocation hooks.
// They must be thread-safe and not use heap related functions.
__attribute__((weak)) void __scudo_allocate_hook(void *ptr, size_t size); __attribute__((weak)) void __scudo_allocate_hook(void *ptr, size_t size);
__attribute__((weak)) void __scudo_deallocate_hook(void *ptr); __attribute__((weak)) void __scudo_deallocate_hook(void *ptr);
// `realloc` involves both deallocation and allocation but they are not reported
// atomically. In one specific case which may keep taking a snapshot right in
// the middle of `realloc` reporting the deallocation and allocation, it may
// confuse the user by missing memory from `realloc`. To alleviate that case,
// define the two `realloc` hooks to get the knowledge of the bundled hook
// calls. These hooks are optional and should only be used when a hooks user
// wants to track reallocs more closely.
//
// See more details in the comment of `realloc` in wrapper_c.inc.
__attribute__((weak)) void
__scudo_realloc_allocate_hook(void *old_ptr, void *new_ptr, size_t size);
__attribute__((weak)) void __scudo_realloc_deallocate_hook(void *old_ptr);
void __scudo_print_stats(void); void __scudo_print_stats(void);
typedef void (*iterate_callback)(uintptr_t base, size_t size, void *arg); typedef void (*iterate_callback)(uintptr_t base, size_t size, void *arg);
@ -73,7 +85,8 @@ typedef void (*iterate_callback)(uintptr_t base, size_t size, void *arg);
// pointer. // pointer.
void __scudo_get_error_info(struct scudo_error_info *error_info, void __scudo_get_error_info(struct scudo_error_info *error_info,
uintptr_t fault_addr, const char *stack_depot, uintptr_t fault_addr, const char *stack_depot,
const char *region_info, const char *ring_buffer, size_t stack_depot_size, const char *region_info,
const char *ring_buffer, size_t ring_buffer_size,
const char *memory, const char *memory_tags, const char *memory, const char *memory_tags,
uintptr_t memory_addr, size_t memory_size); uintptr_t memory_addr, size_t memory_size);

View file

@ -14,6 +14,7 @@
#include "internal_defs.h" #include "internal_defs.h"
#include "linux.h" #include "linux.h"
#include "mutex.h" #include "mutex.h"
#include "report_linux.h"
#include "string_utils.h" #include "string_utils.h"
#include <errno.h> #include <errno.h>
@ -43,6 +44,7 @@ uptr getPageSize() { return static_cast<uptr>(sysconf(_SC_PAGESIZE)); }
void NORETURN die() { abort(); } void NORETURN die() { abort(); }
// TODO: Will be deprecated. Use the interfaces in MemMapLinux instead.
void *map(void *Addr, uptr Size, UNUSED const char *Name, uptr Flags, void *map(void *Addr, uptr Size, UNUSED const char *Name, uptr Flags,
UNUSED MapPlatformData *Data) { UNUSED MapPlatformData *Data) {
int MmapFlags = MAP_PRIVATE | MAP_ANONYMOUS; int MmapFlags = MAP_PRIVATE | MAP_ANONYMOUS;
@ -65,7 +67,7 @@ void *map(void *Addr, uptr Size, UNUSED const char *Name, uptr Flags,
void *P = mmap(Addr, Size, MmapProt, MmapFlags, -1, 0); void *P = mmap(Addr, Size, MmapProt, MmapFlags, -1, 0);
if (P == MAP_FAILED) { if (P == MAP_FAILED) {
if (!(Flags & MAP_ALLOWNOMEM) || errno != ENOMEM) if (!(Flags & MAP_ALLOWNOMEM) || errno != ENOMEM)
dieOnMapUnmapError(errno == ENOMEM ? Size : 0); reportMapError(errno == ENOMEM ? Size : 0);
return nullptr; return nullptr;
} }
#if SCUDO_ANDROID #if SCUDO_ANDROID
@ -75,19 +77,22 @@ void *map(void *Addr, uptr Size, UNUSED const char *Name, uptr Flags,
return P; return P;
} }
// TODO: Will be deprecated. Use the interfaces in MemMapLinux instead.
void unmap(void *Addr, uptr Size, UNUSED uptr Flags, void unmap(void *Addr, uptr Size, UNUSED uptr Flags,
UNUSED MapPlatformData *Data) { UNUSED MapPlatformData *Data) {
if (munmap(Addr, Size) != 0) if (munmap(Addr, Size) != 0)
dieOnMapUnmapError(); reportUnmapError(reinterpret_cast<uptr>(Addr), Size);
} }
// TODO: Will be deprecated. Use the interfaces in MemMapLinux instead.
void setMemoryPermission(uptr Addr, uptr Size, uptr Flags, void setMemoryPermission(uptr Addr, uptr Size, uptr Flags,
UNUSED MapPlatformData *Data) { UNUSED MapPlatformData *Data) {
int Prot = (Flags & MAP_NOACCESS) ? PROT_NONE : (PROT_READ | PROT_WRITE); int Prot = (Flags & MAP_NOACCESS) ? PROT_NONE : (PROT_READ | PROT_WRITE);
if (mprotect(reinterpret_cast<void *>(Addr), Size, Prot) != 0) if (mprotect(reinterpret_cast<void *>(Addr), Size, Prot) != 0)
dieOnMapUnmapError(); reportProtectError(Addr, Size, Prot);
} }
// TODO: Will be deprecated. Use the interfaces in MemMapLinux instead.
void releasePagesToOS(uptr BaseAddress, uptr Offset, uptr Size, void releasePagesToOS(uptr BaseAddress, uptr Offset, uptr Size,
UNUSED MapPlatformData *Data) { UNUSED MapPlatformData *Data) {
void *Addr = reinterpret_cast<void *>(BaseAddress + Offset); void *Addr = reinterpret_cast<void *>(BaseAddress + Offset);
@ -104,12 +109,14 @@ enum State : u32 { Unlocked = 0, Locked = 1, Sleeping = 2 };
} }
bool HybridMutex::tryLock() { bool HybridMutex::tryLock() {
return atomic_compare_exchange(&M, Unlocked, Locked) == Unlocked; return atomic_compare_exchange_strong(&M, Unlocked, Locked,
memory_order_acquire) == Unlocked;
} }
// The following is based on https://akkadia.org/drepper/futex.pdf. // The following is based on https://akkadia.org/drepper/futex.pdf.
void HybridMutex::lockSlow() { void HybridMutex::lockSlow() {
u32 V = atomic_compare_exchange(&M, Unlocked, Locked); u32 V = atomic_compare_exchange_strong(&M, Unlocked, Locked,
memory_order_acquire);
if (V == Unlocked) if (V == Unlocked)
return; return;
if (V != Sleeping) if (V != Sleeping)
@ -197,39 +204,6 @@ bool getRandom(void *Buffer, uptr Length, UNUSED bool Blocking) {
extern "C" WEAK int async_safe_write_log(int pri, const char *tag, extern "C" WEAK int async_safe_write_log(int pri, const char *tag,
const char *msg); const char *msg);
static uptr GetRSSFromBuffer(const char *Buf) {
// The format of the file is:
// 1084 89 69 11 0 79 0
// We need the second number which is RSS in pages.
const char *Pos = Buf;
// Skip the first number.
while (*Pos >= '0' && *Pos <= '9')
Pos++;
// Skip whitespaces.
while (!(*Pos >= '0' && *Pos <= '9') && *Pos != 0)
Pos++;
// Read the number.
u64 Rss = 0;
for (; *Pos >= '0' && *Pos <= '9'; Pos++)
Rss = Rss * 10 + static_cast<u64>(*Pos) - '0';
return static_cast<uptr>(Rss * getPageSizeCached());
}
uptr GetRSS() {
// TODO: We currently use sanitizer_common's GetRSS which reads the
// RSS from /proc/self/statm by default. We might want to
// call getrusage directly, even if it's less accurate.
auto Fd = open("/proc/self/statm", O_RDONLY);
char Buf[64];
s64 Len = read(Fd, Buf, sizeof(Buf) - 1);
close(Fd);
if (Len <= 0)
return 0;
Buf[Len] = 0;
return GetRSSFromBuffer(Buf);
}
void outputRaw(const char *Buffer) { void outputRaw(const char *Buffer) {
if (&async_safe_write_log) { if (&async_safe_write_log) {
constexpr s32 AndroidLogInfo = 4; constexpr s32 AndroidLogInfo = 4;

View file

@ -22,80 +22,13 @@ template <class SizeClassAllocator> struct SizeClassAllocatorLocalCache {
typedef typename SizeClassAllocator::SizeClassMap SizeClassMap; typedef typename SizeClassAllocator::SizeClassMap SizeClassMap;
typedef typename SizeClassAllocator::CompactPtrT CompactPtrT; typedef typename SizeClassAllocator::CompactPtrT CompactPtrT;
struct TransferBatch {
static const u16 MaxNumCached = SizeClassMap::MaxNumCachedHint;
void setFromArray(CompactPtrT *Array, u16 N) {
DCHECK_LE(N, MaxNumCached);
Count = N;
memcpy(Batch, Array, sizeof(Batch[0]) * Count);
}
void appendFromArray(CompactPtrT *Array, u16 N) {
DCHECK_LE(N, MaxNumCached - Count);
memcpy(Batch + Count, Array, sizeof(Batch[0]) * N);
// u16 will be promoted to int by arithmetic type conversion.
Count = static_cast<u16>(Count + N);
}
void appendFromTransferBatch(TransferBatch *B, u16 N) {
DCHECK_LE(N, MaxNumCached - Count);
DCHECK_GE(B->Count, N);
// Append from the back of `B`.
memcpy(Batch + Count, B->Batch + (B->Count - N), sizeof(Batch[0]) * N);
// u16 will be promoted to int by arithmetic type conversion.
Count = static_cast<u16>(Count + N);
B->Count = static_cast<u16>(B->Count - N);
}
void clear() { Count = 0; }
void add(CompactPtrT P) {
DCHECK_LT(Count, MaxNumCached);
Batch[Count++] = P;
}
void copyToArray(CompactPtrT *Array) const {
memcpy(Array, Batch, sizeof(Batch[0]) * Count);
}
u16 getCount() const { return Count; }
bool isEmpty() const { return Count == 0U; }
CompactPtrT get(u16 I) const {
DCHECK_LE(I, Count);
return Batch[I];
}
static u16 getMaxCached(uptr Size) {
return Min(MaxNumCached, SizeClassMap::getMaxCachedHint(Size));
}
TransferBatch *Next;
private:
CompactPtrT Batch[MaxNumCached];
u16 Count;
};
// A BatchGroup is used to collect blocks. Each group has a group id to
// identify the group kind of contained blocks.
struct BatchGroup {
// `Next` is used by IntrusiveList.
BatchGroup *Next;
// The compact base address of each group
uptr CompactPtrGroupBase;
// Cache value of TransferBatch::getMaxCached()
u16 MaxCachedPerBatch;
// Number of blocks pushed into this group. This is an increment-only
// counter.
uptr PushedBlocks;
// This is used to track how many bytes are not in-use since last time we
// tried to release pages.
uptr BytesInBGAtLastCheckpoint;
// Blocks are managed by TransferBatch in a list.
SinglyLinkedList<TransferBatch> Batches;
};
static_assert(sizeof(BatchGroup) <= sizeof(TransferBatch),
"BatchGroup uses the same class size as TransferBatch");
void init(GlobalStats *S, SizeClassAllocator *A) { void init(GlobalStats *S, SizeClassAllocator *A) {
DCHECK(isEmpty()); DCHECK(isEmpty());
Stats.init(); Stats.init();
if (LIKELY(S)) if (LIKELY(S))
S->link(&Stats); S->link(&Stats);
Allocator = A; Allocator = A;
initCache();
} }
void destroy(GlobalStats *S) { void destroy(GlobalStats *S) {
@ -108,7 +41,9 @@ template <class SizeClassAllocator> struct SizeClassAllocatorLocalCache {
DCHECK_LT(ClassId, NumClasses); DCHECK_LT(ClassId, NumClasses);
PerClass *C = &PerClassArray[ClassId]; PerClass *C = &PerClassArray[ClassId];
if (C->Count == 0) { if (C->Count == 0) {
if (UNLIKELY(!refill(C, ClassId))) // Refill half of the number of max cached.
DCHECK_GT(C->MaxCount / 2, 0U);
if (UNLIKELY(!refill(C, ClassId, C->MaxCount / 2)))
return nullptr; return nullptr;
DCHECK_GT(C->Count, 0); DCHECK_GT(C->Count, 0);
} }
@ -125,9 +60,6 @@ template <class SizeClassAllocator> struct SizeClassAllocatorLocalCache {
bool deallocate(uptr ClassId, void *P) { bool deallocate(uptr ClassId, void *P) {
CHECK_LT(ClassId, NumClasses); CHECK_LT(ClassId, NumClasses);
PerClass *C = &PerClassArray[ClassId]; PerClass *C = &PerClassArray[ClassId];
// We still have to initialize the cache in the event that the first heap
// operation in a thread is a deallocation.
initCacheMaybe(C);
// If the cache is full, drain half of blocks back to the main allocator. // If the cache is full, drain half of blocks back to the main allocator.
const bool NeedToDrainCache = C->Count == C->MaxCount; const bool NeedToDrainCache = C->Count == C->MaxCount;
@ -151,7 +83,7 @@ template <class SizeClassAllocator> struct SizeClassAllocatorLocalCache {
} }
void drain() { void drain() {
// Drain BatchClassId last as createBatch can refill it. // Drain BatchClassId last as it may be needed while draining normal blocks.
for (uptr I = 0; I < NumClasses; ++I) { for (uptr I = 0; I < NumClasses; ++I) {
if (I == BatchClassId) if (I == BatchClassId)
continue; continue;
@ -163,19 +95,11 @@ template <class SizeClassAllocator> struct SizeClassAllocatorLocalCache {
DCHECK(isEmpty()); DCHECK(isEmpty());
} }
TransferBatch *createBatch(uptr ClassId, void *B) { void *getBatchClassBlock() {
if (ClassId != BatchClassId) void *B = allocate(BatchClassId);
B = allocate(BatchClassId);
if (UNLIKELY(!B)) if (UNLIKELY(!B))
reportOutOfMemory(SizeClassAllocator::getSizeByClassId(BatchClassId)); reportOutOfMemory(SizeClassAllocator::getSizeByClassId(BatchClassId));
return reinterpret_cast<TransferBatch *>(B); return B;
}
BatchGroup *createGroup() {
void *Ptr = allocate(BatchClassId);
if (UNLIKELY(!Ptr))
reportOutOfMemory(SizeClassAllocator::getSizeByClassId(BatchClassId));
return reinterpret_cast<BatchGroup *>(Ptr);
} }
LocalStats &getStats() { return Stats; } LocalStats &getStats() { return Stats; }
@ -203,6 +127,11 @@ template <class SizeClassAllocator> struct SizeClassAllocatorLocalCache {
Str->append(" No block is cached.\n"); Str->append(" No block is cached.\n");
} }
static u16 getMaxCached(uptr Size) {
return Min(SizeClassMap::MaxNumCachedHint,
SizeClassMap::getMaxCachedHint(Size));
}
private: private:
static const uptr NumClasses = SizeClassMap::NumClasses; static const uptr NumClasses = SizeClassMap::NumClasses;
static const uptr BatchClassId = SizeClassMap::BatchClassId; static const uptr BatchClassId = SizeClassMap::BatchClassId;
@ -211,24 +140,17 @@ private:
u16 MaxCount; u16 MaxCount;
// Note: ClassSize is zero for the transfer batch. // Note: ClassSize is zero for the transfer batch.
uptr ClassSize; uptr ClassSize;
CompactPtrT Chunks[2 * TransferBatch::MaxNumCached]; CompactPtrT Chunks[2 * SizeClassMap::MaxNumCachedHint];
}; };
PerClass PerClassArray[NumClasses] = {}; PerClass PerClassArray[NumClasses] = {};
LocalStats Stats; LocalStats Stats;
SizeClassAllocator *Allocator = nullptr; SizeClassAllocator *Allocator = nullptr;
ALWAYS_INLINE void initCacheMaybe(PerClass *C) {
if (LIKELY(C->MaxCount))
return;
initCache();
DCHECK_NE(C->MaxCount, 0U);
}
NOINLINE void initCache() { NOINLINE void initCache() {
for (uptr I = 0; I < NumClasses; I++) { for (uptr I = 0; I < NumClasses; I++) {
PerClass *P = &PerClassArray[I]; PerClass *P = &PerClassArray[I];
const uptr Size = SizeClassAllocator::getSizeByClassId(I); const uptr Size = SizeClassAllocator::getSizeByClassId(I);
P->MaxCount = static_cast<u16>(2 * TransferBatch::getMaxCached(Size)); P->MaxCount = static_cast<u16>(2 * getMaxCached(Size));
if (I != BatchClassId) { if (I != BatchClassId) {
P->ClassSize = Size; P->ClassSize = Size;
} else { } else {
@ -244,17 +166,12 @@ private:
deallocate(BatchClassId, B); deallocate(BatchClassId, B);
} }
NOINLINE bool refill(PerClass *C, uptr ClassId) { NOINLINE bool refill(PerClass *C, uptr ClassId, u16 MaxRefill) {
initCacheMaybe(C); const u16 NumBlocksRefilled =
TransferBatch *B = Allocator->popBatch(this, ClassId); Allocator->popBlocks(this, ClassId, C->Chunks, MaxRefill);
if (UNLIKELY(!B)) DCHECK_LE(NumBlocksRefilled, MaxRefill);
return false; C->Count = static_cast<u16>(C->Count + NumBlocksRefilled);
DCHECK_GT(B->getCount(), 0); return NumBlocksRefilled != 0;
C->Count = B->getCount();
B->copyToArray(C->Chunks);
B->clear();
destroyBatch(ClassId, B);
return true;
} }
NOINLINE void drain(PerClass *C, uptr ClassId) { NOINLINE void drain(PerClass *C, uptr ClassId) {

View file

@ -22,6 +22,7 @@
#include "trusty.h" #include "trusty.h"
#include "mem_map_fuchsia.h" #include "mem_map_fuchsia.h"
#include "mem_map_linux.h"
namespace scudo { namespace scudo {
@ -73,10 +74,10 @@ private:
}; };
#if SCUDO_LINUX #if SCUDO_LINUX
using ReservedMemoryT = ReservedMemoryDefault; using ReservedMemoryT = ReservedMemoryLinux;
using MemMapT = ReservedMemoryT::MemMapT; using MemMapT = ReservedMemoryT::MemMapT;
#elif SCUDO_FUCHSIA #elif SCUDO_FUCHSIA
using ReservedMemoryT = ReservedMemoryDefault; using ReservedMemoryT = ReservedMemoryFuchsia;
using MemMapT = ReservedMemoryT::MemMapT; using MemMapT = ReservedMemoryT::MemMapT;
#elif SCUDO_TRUSTY #elif SCUDO_TRUSTY
using ReservedMemoryT = ReservedMemoryDefault; using ReservedMemoryT = ReservedMemoryDefault;

View file

@ -41,7 +41,7 @@ static void setVmoName(zx_handle_t Vmo, const char *Name) {
static uptr getRootVmarBase() { static uptr getRootVmarBase() {
static atomic_uptr CachedResult = {0}; static atomic_uptr CachedResult = {0};
uptr Result = atomic_load_relaxed(&CachedResult); uptr Result = atomic_load(&CachedResult, memory_order_acquire);
if (UNLIKELY(!Result)) { if (UNLIKELY(!Result)) {
zx_info_vmar_t VmarInfo; zx_info_vmar_t VmarInfo;
zx_status_t Status = zx_status_t Status =
@ -50,7 +50,7 @@ static uptr getRootVmarBase() {
CHECK_EQ(Status, ZX_OK); CHECK_EQ(Status, ZX_OK);
CHECK_NE(VmarInfo.base, 0); CHECK_NE(VmarInfo.base, 0);
atomic_store_relaxed(&CachedResult, VmarInfo.base); atomic_store(&CachedResult, VmarInfo.base, memory_order_release);
Result = VmarInfo.base; Result = VmarInfo.base;
} }
@ -61,7 +61,7 @@ static uptr getRootVmarBase() {
static zx_handle_t getPlaceholderVmo() { static zx_handle_t getPlaceholderVmo() {
static atomic_u32 StoredVmo = {ZX_HANDLE_INVALID}; static atomic_u32 StoredVmo = {ZX_HANDLE_INVALID};
zx_handle_t Vmo = atomic_load_relaxed(&StoredVmo); zx_handle_t Vmo = atomic_load(&StoredVmo, memory_order_acquire);
if (UNLIKELY(Vmo == ZX_HANDLE_INVALID)) { if (UNLIKELY(Vmo == ZX_HANDLE_INVALID)) {
// Create a zero-sized placeholder VMO. // Create a zero-sized placeholder VMO.
zx_status_t Status = _zx_vmo_create(0, 0, &Vmo); zx_status_t Status = _zx_vmo_create(0, 0, &Vmo);
@ -72,9 +72,9 @@ static zx_handle_t getPlaceholderVmo() {
// Atomically store its handle. If some other thread wins the race, use its // Atomically store its handle. If some other thread wins the race, use its
// handle and discard ours. // handle and discard ours.
zx_handle_t OldValue = zx_handle_t OldValue = atomic_compare_exchange_strong(
atomic_compare_exchange(&StoredVmo, ZX_HANDLE_INVALID, Vmo); &StoredVmo, ZX_HANDLE_INVALID, Vmo, memory_order_acq_rel);
if (OldValue != ZX_HANDLE_INVALID) { if (UNLIKELY(OldValue != ZX_HANDLE_INVALID)) {
Status = _zx_handle_close(Vmo); Status = _zx_handle_close(Vmo);
CHECK_EQ(Status, ZX_OK); CHECK_EQ(Status, ZX_OK);

View file

@ -0,0 +1,153 @@
//===-- mem_map_linux.cpp ---------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "platform.h"
#if SCUDO_LINUX
#include "mem_map_linux.h"
#include "common.h"
#include "internal_defs.h"
#include "linux.h"
#include "mutex.h"
#include "report_linux.h"
#include "string_utils.h"
#include <errno.h>
#include <fcntl.h>
#include <linux/futex.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#if SCUDO_ANDROID
// TODO(chiahungduan): Review if we still need the followings macros.
#include <sys/prctl.h>
// Definitions of prctl arguments to set a vma name in Android kernels.
#define ANDROID_PR_SET_VMA 0x53564d41
#define ANDROID_PR_SET_VMA_ANON_NAME 0
#endif
namespace scudo {
static void *mmapWrapper(uptr Addr, uptr Size, const char *Name, uptr Flags) {
int MmapFlags = MAP_PRIVATE | MAP_ANONYMOUS;
int MmapProt;
if (Flags & MAP_NOACCESS) {
MmapFlags |= MAP_NORESERVE;
MmapProt = PROT_NONE;
} else {
MmapProt = PROT_READ | PROT_WRITE;
}
#if defined(__aarch64__)
#ifndef PROT_MTE
#define PROT_MTE 0x20
#endif
if (Flags & MAP_MEMTAG)
MmapProt |= PROT_MTE;
#endif
if (Addr)
MmapFlags |= MAP_FIXED;
void *P =
mmap(reinterpret_cast<void *>(Addr), Size, MmapProt, MmapFlags, -1, 0);
if (P == MAP_FAILED) {
if (!(Flags & MAP_ALLOWNOMEM) || errno != ENOMEM)
reportMapError(errno == ENOMEM ? Size : 0);
return nullptr;
}
#if SCUDO_ANDROID
if (Name)
prctl(ANDROID_PR_SET_VMA, ANDROID_PR_SET_VMA_ANON_NAME, P, Size, Name);
#else
(void)Name;
#endif
return P;
}
bool MemMapLinux::mapImpl(uptr Addr, uptr Size, const char *Name, uptr Flags) {
void *P = mmapWrapper(Addr, Size, Name, Flags);
if (P == nullptr)
return false;
MapBase = reinterpret_cast<uptr>(P);
MapCapacity = Size;
return true;
}
void MemMapLinux::unmapImpl(uptr Addr, uptr Size) {
// If we unmap all the pages, also mark `MapBase` to 0 to indicate invalid
// status.
if (Size == MapCapacity) {
MapBase = MapCapacity = 0;
} else {
// This is partial unmap and is unmapping the pages from the beginning,
// shift `MapBase` to the new base.
if (MapBase == Addr)
MapBase = Addr + Size;
MapCapacity -= Size;
}
if (munmap(reinterpret_cast<void *>(Addr), Size) != 0)
reportUnmapError(Addr, Size);
}
bool MemMapLinux::remapImpl(uptr Addr, uptr Size, const char *Name,
uptr Flags) {
void *P = mmapWrapper(Addr, Size, Name, Flags);
if (reinterpret_cast<uptr>(P) != Addr)
reportMapError();
return true;
}
void MemMapLinux::setMemoryPermissionImpl(uptr Addr, uptr Size, uptr Flags) {
int Prot = (Flags & MAP_NOACCESS) ? PROT_NONE : (PROT_READ | PROT_WRITE);
if (mprotect(reinterpret_cast<void *>(Addr), Size, Prot) != 0)
reportProtectError(Addr, Size, Prot);
}
void MemMapLinux::releaseAndZeroPagesToOSImpl(uptr From, uptr Size) {
void *Addr = reinterpret_cast<void *>(From);
while (madvise(Addr, Size, MADV_DONTNEED) == -1 && errno == EAGAIN) {
}
}
bool ReservedMemoryLinux::createImpl(uptr Addr, uptr Size, const char *Name,
uptr Flags) {
ReservedMemoryLinux::MemMapT MemMap;
if (!MemMap.map(Addr, Size, Name, Flags | MAP_NOACCESS))
return false;
MapBase = MemMap.getBase();
MapCapacity = MemMap.getCapacity();
return true;
}
void ReservedMemoryLinux::releaseImpl() {
if (munmap(reinterpret_cast<void *>(getBase()), getCapacity()) != 0)
reportUnmapError(getBase(), getCapacity());
}
ReservedMemoryLinux::MemMapT ReservedMemoryLinux::dispatchImpl(uptr Addr,
uptr Size) {
return ReservedMemoryLinux::MemMapT(Addr, Size);
}
} // namespace scudo
#endif // SCUDO_LINUX

View file

@ -0,0 +1,67 @@
//===-- mem_map_linux.h -----------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef SCUDO_MEM_MAP_LINUX_H_
#define SCUDO_MEM_MAP_LINUX_H_
#include "platform.h"
#if SCUDO_LINUX
#include "common.h"
#include "mem_map_base.h"
namespace scudo {
class MemMapLinux final : public MemMapBase<MemMapLinux> {
public:
constexpr MemMapLinux() = default;
MemMapLinux(uptr Base, uptr Capacity)
: MapBase(Base), MapCapacity(Capacity) {}
// Impls for base functions.
bool mapImpl(uptr Addr, uptr Size, const char *Name, uptr Flags = 0);
void unmapImpl(uptr Addr, uptr Size);
bool remapImpl(uptr Addr, uptr Size, const char *Name, uptr Flags = 0);
void setMemoryPermissionImpl(uptr Addr, uptr Size, uptr Flags);
void releasePagesToOSImpl(uptr From, uptr Size) {
return releaseAndZeroPagesToOSImpl(From, Size);
}
void releaseAndZeroPagesToOSImpl(uptr From, uptr Size);
uptr getBaseImpl() { return MapBase; }
uptr getCapacityImpl() { return MapCapacity; }
private:
uptr MapBase = 0;
uptr MapCapacity = 0;
};
// This will be deprecated when every allocator has been supported by each
// platform's `MemMap` implementation.
class ReservedMemoryLinux final
: public ReservedMemory<ReservedMemoryLinux, MemMapLinux> {
public:
// The following two are the Impls for function in `MemMapBase`.
uptr getBaseImpl() { return MapBase; }
uptr getCapacityImpl() { return MapCapacity; }
// These threes are specific to `ReservedMemory`.
bool createImpl(uptr Addr, uptr Size, const char *Name, uptr Flags);
void releaseImpl();
MemMapT dispatchImpl(uptr Addr, uptr Size);
private:
uptr MapBase = 0;
uptr MapCapacity = 0;
};
} // namespace scudo
#endif // SCUDO_LINUX
#endif // SCUDO_MEM_MAP_LINUX_H_

View file

@ -35,7 +35,7 @@ public:
#pragma nounroll #pragma nounroll
#endif #endif
for (u8 I = 0U; I < NumberOfTries; I++) { for (u8 I = 0U; I < NumberOfTries; I++) {
yieldProcessor(NumberOfYields); delayLoop();
if (tryLock()) if (tryLock())
return; return;
} }
@ -53,10 +53,23 @@ public:
} }
private: private:
void delayLoop() {
// The value comes from the average time spent in accessing caches (which
// are the fastest operations) so that we are unlikely to wait too long for
// fast operations.
constexpr u32 SpinTimes = 16;
volatile u32 V = 0;
for (u32 I = 0; I < SpinTimes; ++I) {
u32 Tmp = V + 1;
V = Tmp;
}
}
void assertHeldImpl(); void assertHeldImpl();
static constexpr u8 NumberOfTries = 8U; // TODO(chiahungduan): Adapt this value based on scenarios. E.g., primary and
static constexpr u8 NumberOfYields = 8U; // secondary allocator have different allocation times.
static constexpr u8 NumberOfTries = 32U;
#if SCUDO_LINUX #if SCUDO_LINUX
atomic_u32 M = {}; atomic_u32 M = {};

View file

@ -38,7 +38,7 @@ struct Options {
} }
}; };
template <typename Config> bool useMemoryTagging(Options Options) { template <typename Config> bool useMemoryTagging(const Options &Options) {
return allocatorSupportsMemoryTagging<Config>() && return allocatorSupportsMemoryTagging<Config>() &&
Options.get(OptionBit::UseMemoryTagging); Options.get(OptionBit::UseMemoryTagging);
} }

View file

@ -63,6 +63,20 @@
#define SCUDO_CAN_USE_MTE (SCUDO_LINUX || SCUDO_TRUSTY) #define SCUDO_CAN_USE_MTE (SCUDO_LINUX || SCUDO_TRUSTY)
#endif #endif
// Use smaller table sizes for fuzzing in order to reduce input size.
// Trusty just has less available memory.
#ifndef SCUDO_SMALL_STACK_DEPOT
#if defined(SCUDO_FUZZ) || SCUDO_TRUSTY
#define SCUDO_SMALL_STACK_DEPOT 1
#else
#define SCUDO_SMALL_STACK_DEPOT 0
#endif
#endif
#ifndef SCUDO_ENABLE_HOOKS
#define SCUDO_ENABLE_HOOKS 0
#endif
#ifndef SCUDO_MIN_ALIGNMENT_LOG #ifndef SCUDO_MIN_ALIGNMENT_LOG
// We force malloc-type functions to be aligned to std::max_align_t, but there // We force malloc-type functions to be aligned to std::max_align_t, but there
// is no reason why the minimum alignment for all other functions can't be 8 // is no reason why the minimum alignment for all other functions can't be 8

View file

@ -9,6 +9,7 @@
#ifndef SCUDO_PRIMARY32_H_ #ifndef SCUDO_PRIMARY32_H_
#define SCUDO_PRIMARY32_H_ #define SCUDO_PRIMARY32_H_
#include "allocator_common.h"
#include "bytemap.h" #include "bytemap.h"
#include "common.h" #include "common.h"
#include "list.h" #include "list.h"
@ -53,12 +54,15 @@ public:
""); "");
typedef SizeClassAllocator32<Config> ThisT; typedef SizeClassAllocator32<Config> ThisT;
typedef SizeClassAllocatorLocalCache<ThisT> CacheT; typedef SizeClassAllocatorLocalCache<ThisT> CacheT;
typedef typename CacheT::TransferBatch TransferBatch; typedef TransferBatch<ThisT> TransferBatchT;
typedef typename CacheT::BatchGroup BatchGroup; typedef BatchGroup<ThisT> BatchGroupT;
static_assert(sizeof(BatchGroupT) <= sizeof(TransferBatchT),
"BatchGroupT uses the same class size as TransferBatchT");
static uptr getSizeByClassId(uptr ClassId) { static uptr getSizeByClassId(uptr ClassId) {
return (ClassId == SizeClassMap::BatchClassId) return (ClassId == SizeClassMap::BatchClassId)
? sizeof(TransferBatch) ? sizeof(TransferBatchT)
: SizeClassMap::getSizeByClassId(ClassId); : SizeClassMap::getSizeByClassId(ClassId);
} }
@ -126,7 +130,7 @@ public:
SizeClassInfo *Sci = getSizeClassInfo(I); SizeClassInfo *Sci = getSizeClassInfo(I);
ScopedLock L1(Sci->Mutex); ScopedLock L1(Sci->Mutex);
uptr TotalBlocks = 0; uptr TotalBlocks = 0;
for (BatchGroup &BG : Sci->FreeListInfo.BlockList) { for (BatchGroupT &BG : Sci->FreeListInfo.BlockList) {
// `BG::Batches` are `TransferBatches`. +1 for `BatchGroup`. // `BG::Batches` are `TransferBatches`. +1 for `BatchGroup`.
BatchClassUsedInFreeLists += BG.Batches.size() + 1; BatchClassUsedInFreeLists += BG.Batches.size() + 1;
for (const auto &It : BG.Batches) for (const auto &It : BG.Batches)
@ -141,7 +145,7 @@ public:
SizeClassInfo *Sci = getSizeClassInfo(SizeClassMap::BatchClassId); SizeClassInfo *Sci = getSizeClassInfo(SizeClassMap::BatchClassId);
ScopedLock L1(Sci->Mutex); ScopedLock L1(Sci->Mutex);
uptr TotalBlocks = 0; uptr TotalBlocks = 0;
for (BatchGroup &BG : Sci->FreeListInfo.BlockList) { for (BatchGroupT &BG : Sci->FreeListInfo.BlockList) {
if (LIKELY(!BG.Batches.empty())) { if (LIKELY(!BG.Batches.empty())) {
for (const auto &It : BG.Batches) for (const auto &It : BG.Batches)
TotalBlocks += It.getCount(); TotalBlocks += It.getCount();
@ -187,11 +191,30 @@ public:
return BlockSize > PageSize; return BlockSize > PageSize;
} }
TransferBatch *popBatch(CacheT *C, uptr ClassId) { // Note that the `MaxBlockCount` will be used when we support arbitrary blocks
// count. Now it's the same as the number of blocks stored in the
// `TransferBatch`.
u16 popBlocks(CacheT *C, uptr ClassId, CompactPtrT *ToArray,
UNUSED const u16 MaxBlockCount) {
TransferBatchT *B = popBatch(C, ClassId);
if (!B)
return 0;
const u16 Count = B->getCount();
DCHECK_GT(Count, 0U);
B->moveToArray(ToArray);
if (ClassId != SizeClassMap::BatchClassId)
C->deallocate(SizeClassMap::BatchClassId, B);
return Count;
}
TransferBatchT *popBatch(CacheT *C, uptr ClassId) {
DCHECK_LT(ClassId, NumClasses); DCHECK_LT(ClassId, NumClasses);
SizeClassInfo *Sci = getSizeClassInfo(ClassId); SizeClassInfo *Sci = getSizeClassInfo(ClassId);
ScopedLock L(Sci->Mutex); ScopedLock L(Sci->Mutex);
TransferBatch *B = popBatchImpl(C, ClassId, Sci); TransferBatchT *B = popBatchImpl(C, ClassId, Sci);
if (UNLIKELY(!B)) { if (UNLIKELY(!B)) {
if (UNLIKELY(!populateFreeList(C, ClassId, Sci))) if (UNLIKELY(!populateFreeList(C, ClassId, Sci)))
return nullptr; return nullptr;
@ -311,6 +334,18 @@ public:
} }
} }
void getFragmentationInfo(ScopedString *Str) {
Str->append(
"Fragmentation Stats: SizeClassAllocator32: page size = %zu bytes\n",
getPageSizeCached());
for (uptr I = 1; I < NumClasses; I++) {
SizeClassInfo *Sci = getSizeClassInfo(I);
ScopedLock L(Sci->Mutex);
getSizeClassFragmentationInfo(Sci, I, Str);
}
}
bool setOption(Option O, sptr Value) { bool setOption(Option O, sptr Value) {
if (O == Option::ReleaseInterval) { if (O == Option::ReleaseInterval) {
const s32 Interval = Max(Min(static_cast<s32>(Value), const s32 Interval = Max(Min(static_cast<s32>(Value),
@ -369,7 +404,7 @@ private:
}; };
struct BlocksInfo { struct BlocksInfo {
SinglyLinkedList<BatchGroup> BlockList = {}; SinglyLinkedList<BatchGroupT> BlockList = {};
uptr PoppedBlocks = 0; uptr PoppedBlocks = 0;
uptr PushedBlocks = 0; uptr PushedBlocks = 0;
}; };
@ -493,11 +528,11 @@ private:
// reusable and don't need additional space for them. // reusable and don't need additional space for them.
Sci->FreeListInfo.PushedBlocks += Size; Sci->FreeListInfo.PushedBlocks += Size;
BatchGroup *BG = Sci->FreeListInfo.BlockList.front(); BatchGroupT *BG = Sci->FreeListInfo.BlockList.front();
if (BG == nullptr) { if (BG == nullptr) {
// Construct `BatchGroup` on the last element. // Construct `BatchGroup` on the last element.
BG = reinterpret_cast<BatchGroup *>( BG = reinterpret_cast<BatchGroupT *>(
decompactPtr(SizeClassMap::BatchClassId, Array[Size - 1])); decompactPtr(SizeClassMap::BatchClassId, Array[Size - 1]));
--Size; --Size;
BG->Batches.clear(); BG->Batches.clear();
@ -508,8 +543,8 @@ private:
// from `CreateGroup` in `pushBlocksImpl` // from `CreateGroup` in `pushBlocksImpl`
BG->PushedBlocks = 1; BG->PushedBlocks = 1;
BG->BytesInBGAtLastCheckpoint = 0; BG->BytesInBGAtLastCheckpoint = 0;
BG->MaxCachedPerBatch = TransferBatch::getMaxCached( BG->MaxCachedPerBatch =
getSizeByClassId(SizeClassMap::BatchClassId)); CacheT::getMaxCached(getSizeByClassId(SizeClassMap::BatchClassId));
Sci->FreeListInfo.BlockList.push_front(BG); Sci->FreeListInfo.BlockList.push_front(BG);
} }
@ -522,7 +557,7 @@ private:
// 2. Only 1 block is pushed when the freelist is empty. // 2. Only 1 block is pushed when the freelist is empty.
if (BG->Batches.empty()) { if (BG->Batches.empty()) {
// Construct the `TransferBatch` on the last element. // Construct the `TransferBatch` on the last element.
TransferBatch *TB = reinterpret_cast<TransferBatch *>( TransferBatchT *TB = reinterpret_cast<TransferBatchT *>(
decompactPtr(SizeClassMap::BatchClassId, Array[Size - 1])); decompactPtr(SizeClassMap::BatchClassId, Array[Size - 1]));
TB->clear(); TB->clear();
// As mentioned above, addresses of `TransferBatch` and `BatchGroup` are // As mentioned above, addresses of `TransferBatch` and `BatchGroup` are
@ -537,14 +572,14 @@ private:
BG->Batches.push_front(TB); BG->Batches.push_front(TB);
} }
TransferBatch *CurBatch = BG->Batches.front(); TransferBatchT *CurBatch = BG->Batches.front();
DCHECK_NE(CurBatch, nullptr); DCHECK_NE(CurBatch, nullptr);
for (u32 I = 0; I < Size;) { for (u32 I = 0; I < Size;) {
u16 UnusedSlots = u16 UnusedSlots =
static_cast<u16>(BG->MaxCachedPerBatch - CurBatch->getCount()); static_cast<u16>(BG->MaxCachedPerBatch - CurBatch->getCount());
if (UnusedSlots == 0) { if (UnusedSlots == 0) {
CurBatch = reinterpret_cast<TransferBatch *>( CurBatch = reinterpret_cast<TransferBatchT *>(
decompactPtr(SizeClassMap::BatchClassId, Array[I])); decompactPtr(SizeClassMap::BatchClassId, Array[I]));
CurBatch->clear(); CurBatch->clear();
// Self-contained // Self-contained
@ -588,24 +623,25 @@ private:
DCHECK_GT(Size, 0U); DCHECK_GT(Size, 0U);
auto CreateGroup = [&](uptr CompactPtrGroupBase) { auto CreateGroup = [&](uptr CompactPtrGroupBase) {
BatchGroup *BG = C->createGroup(); BatchGroupT *BG =
reinterpret_cast<BatchGroupT *>(C->getBatchClassBlock());
BG->Batches.clear(); BG->Batches.clear();
TransferBatch *TB = C->createBatch(ClassId, nullptr); TransferBatchT *TB =
reinterpret_cast<TransferBatchT *>(C->getBatchClassBlock());
TB->clear(); TB->clear();
BG->CompactPtrGroupBase = CompactPtrGroupBase; BG->CompactPtrGroupBase = CompactPtrGroupBase;
BG->Batches.push_front(TB); BG->Batches.push_front(TB);
BG->PushedBlocks = 0; BG->PushedBlocks = 0;
BG->BytesInBGAtLastCheckpoint = 0; BG->BytesInBGAtLastCheckpoint = 0;
BG->MaxCachedPerBatch = BG->MaxCachedPerBatch = CacheT::getMaxCached(getSizeByClassId(ClassId));
TransferBatch::getMaxCached(getSizeByClassId(ClassId));
return BG; return BG;
}; };
auto InsertBlocks = [&](BatchGroup *BG, CompactPtrT *Array, u32 Size) { auto InsertBlocks = [&](BatchGroupT *BG, CompactPtrT *Array, u32 Size) {
SinglyLinkedList<TransferBatch> &Batches = BG->Batches; SinglyLinkedList<TransferBatchT> &Batches = BG->Batches;
TransferBatch *CurBatch = Batches.front(); TransferBatchT *CurBatch = Batches.front();
DCHECK_NE(CurBatch, nullptr); DCHECK_NE(CurBatch, nullptr);
for (u32 I = 0; I < Size;) { for (u32 I = 0; I < Size;) {
@ -613,9 +649,8 @@ private:
u16 UnusedSlots = u16 UnusedSlots =
static_cast<u16>(BG->MaxCachedPerBatch - CurBatch->getCount()); static_cast<u16>(BG->MaxCachedPerBatch - CurBatch->getCount());
if (UnusedSlots == 0) { if (UnusedSlots == 0) {
CurBatch = C->createBatch( CurBatch =
ClassId, reinterpret_cast<TransferBatchT *>(C->getBatchClassBlock());
reinterpret_cast<void *>(decompactPtr(ClassId, Array[I])));
CurBatch->clear(); CurBatch->clear();
Batches.push_front(CurBatch); Batches.push_front(CurBatch);
UnusedSlots = BG->MaxCachedPerBatch; UnusedSlots = BG->MaxCachedPerBatch;
@ -630,11 +665,11 @@ private:
}; };
Sci->FreeListInfo.PushedBlocks += Size; Sci->FreeListInfo.PushedBlocks += Size;
BatchGroup *Cur = Sci->FreeListInfo.BlockList.front(); BatchGroupT *Cur = Sci->FreeListInfo.BlockList.front();
// In the following, `Cur` always points to the BatchGroup for blocks that // In the following, `Cur` always points to the BatchGroup for blocks that
// will be pushed next. `Prev` is the element right before `Cur`. // will be pushed next. `Prev` is the element right before `Cur`.
BatchGroup *Prev = nullptr; BatchGroupT *Prev = nullptr;
while (Cur != nullptr && while (Cur != nullptr &&
compactPtrGroupBase(Array[0]) > Cur->CompactPtrGroupBase) { compactPtrGroupBase(Array[0]) > Cur->CompactPtrGroupBase) {
@ -695,22 +730,22 @@ private:
// group id will be considered first. // group id will be considered first.
// //
// The region mutex needs to be held while calling this method. // The region mutex needs to be held while calling this method.
TransferBatch *popBatchImpl(CacheT *C, uptr ClassId, SizeClassInfo *Sci) TransferBatchT *popBatchImpl(CacheT *C, uptr ClassId, SizeClassInfo *Sci)
REQUIRES(Sci->Mutex) { REQUIRES(Sci->Mutex) {
if (Sci->FreeListInfo.BlockList.empty()) if (Sci->FreeListInfo.BlockList.empty())
return nullptr; return nullptr;
SinglyLinkedList<TransferBatch> &Batches = SinglyLinkedList<TransferBatchT> &Batches =
Sci->FreeListInfo.BlockList.front()->Batches; Sci->FreeListInfo.BlockList.front()->Batches;
if (Batches.empty()) { if (Batches.empty()) {
DCHECK_EQ(ClassId, SizeClassMap::BatchClassId); DCHECK_EQ(ClassId, SizeClassMap::BatchClassId);
BatchGroup *BG = Sci->FreeListInfo.BlockList.front(); BatchGroupT *BG = Sci->FreeListInfo.BlockList.front();
Sci->FreeListInfo.BlockList.pop_front(); Sci->FreeListInfo.BlockList.pop_front();
// Block used by `BatchGroup` is from BatchClassId. Turn the block into // Block used by `BatchGroup` is from BatchClassId. Turn the block into
// `TransferBatch` with single block. // `TransferBatch` with single block.
TransferBatch *TB = reinterpret_cast<TransferBatch *>(BG); TransferBatchT *TB = reinterpret_cast<TransferBatchT *>(BG);
TB->clear(); TB->clear();
TB->add( TB->add(
compactPtr(SizeClassMap::BatchClassId, reinterpret_cast<uptr>(TB))); compactPtr(SizeClassMap::BatchClassId, reinterpret_cast<uptr>(TB)));
@ -718,13 +753,13 @@ private:
return TB; return TB;
} }
TransferBatch *B = Batches.front(); TransferBatchT *B = Batches.front();
Batches.pop_front(); Batches.pop_front();
DCHECK_NE(B, nullptr); DCHECK_NE(B, nullptr);
DCHECK_GT(B->getCount(), 0U); DCHECK_GT(B->getCount(), 0U);
if (Batches.empty()) { if (Batches.empty()) {
BatchGroup *BG = Sci->FreeListInfo.BlockList.front(); BatchGroupT *BG = Sci->FreeListInfo.BlockList.front();
Sci->FreeListInfo.BlockList.pop_front(); Sci->FreeListInfo.BlockList.pop_front();
// We don't keep BatchGroup with zero blocks to avoid empty-checking while // We don't keep BatchGroup with zero blocks to avoid empty-checking while
@ -763,7 +798,7 @@ private:
} }
const uptr Size = getSizeByClassId(ClassId); const uptr Size = getSizeByClassId(ClassId);
const u16 MaxCount = TransferBatch::getMaxCached(Size); const u16 MaxCount = CacheT::getMaxCached(Size);
DCHECK_GT(MaxCount, 0U); DCHECK_GT(MaxCount, 0U);
// The maximum number of blocks we should carve in the region is dictated // The maximum number of blocks we should carve in the region is dictated
// by the maximum number of batches we want to fill, and the amount of // by the maximum number of batches we want to fill, and the amount of
@ -776,7 +811,7 @@ private:
DCHECK_GT(NumberOfBlocks, 0U); DCHECK_GT(NumberOfBlocks, 0U);
constexpr u32 ShuffleArraySize = constexpr u32 ShuffleArraySize =
MaxNumBatches * TransferBatch::MaxNumCached; MaxNumBatches * TransferBatchT::MaxNumCached;
// Fill the transfer batches and put them in the size-class freelist. We // Fill the transfer batches and put them in the size-class freelist. We
// need to randomize the blocks for security purposes, so we first fill a // need to randomize the blocks for security purposes, so we first fill a
// local array that we then shuffle before populating the batches. // local array that we then shuffle before populating the batches.
@ -856,11 +891,60 @@ private:
PushedBytesDelta >> 10); PushedBytesDelta >> 10);
} }
void getSizeClassFragmentationInfo(SizeClassInfo *Sci, uptr ClassId,
ScopedString *Str) REQUIRES(Sci->Mutex) {
const uptr BlockSize = getSizeByClassId(ClassId);
const uptr First = Sci->MinRegionIndex;
const uptr Last = Sci->MaxRegionIndex;
const uptr Base = First * RegionSize;
const uptr NumberOfRegions = Last - First + 1U;
auto SkipRegion = [this, First, ClassId](uptr RegionIndex) {
ScopedLock L(ByteMapMutex);
return (PossibleRegions[First + RegionIndex] - 1U) != ClassId;
};
FragmentationRecorder Recorder;
if (!Sci->FreeListInfo.BlockList.empty()) {
PageReleaseContext Context =
markFreeBlocks(Sci, ClassId, BlockSize, Base, NumberOfRegions,
ReleaseToOS::ForceAll);
releaseFreeMemoryToOS(Context, Recorder, SkipRegion);
}
const uptr PageSize = getPageSizeCached();
const uptr TotalBlocks = Sci->AllocatedUser / BlockSize;
const uptr InUseBlocks =
Sci->FreeListInfo.PoppedBlocks - Sci->FreeListInfo.PushedBlocks;
uptr AllocatedPagesCount = 0;
if (TotalBlocks != 0U) {
for (uptr I = 0; I < NumberOfRegions; ++I) {
if (SkipRegion(I))
continue;
AllocatedPagesCount += RegionSize / PageSize;
}
DCHECK_NE(AllocatedPagesCount, 0U);
}
DCHECK_GE(AllocatedPagesCount, Recorder.getReleasedPagesCount());
const uptr InUsePages =
AllocatedPagesCount - Recorder.getReleasedPagesCount();
const uptr InUseBytes = InUsePages * PageSize;
uptr Integral;
uptr Fractional;
computePercentage(BlockSize * InUseBlocks, InUsePages * PageSize, &Integral,
&Fractional);
Str->append(" %02zu (%6zu): inuse/total blocks: %6zu/%6zu inuse/total "
"pages: %6zu/%6zu inuse bytes: %6zuK util: %3zu.%02zu%%\n",
ClassId, BlockSize, InUseBlocks, TotalBlocks, InUsePages,
AllocatedPagesCount, InUseBytes >> 10, Integral, Fractional);
}
NOINLINE uptr releaseToOSMaybe(SizeClassInfo *Sci, uptr ClassId, NOINLINE uptr releaseToOSMaybe(SizeClassInfo *Sci, uptr ClassId,
ReleaseToOS ReleaseType = ReleaseToOS::Normal) ReleaseToOS ReleaseType = ReleaseToOS::Normal)
REQUIRES(Sci->Mutex) { REQUIRES(Sci->Mutex) {
const uptr BlockSize = getSizeByClassId(ClassId); const uptr BlockSize = getSizeByClassId(ClassId);
const uptr PageSize = getPageSizeCached();
DCHECK_GE(Sci->FreeListInfo.PoppedBlocks, Sci->FreeListInfo.PushedBlocks); DCHECK_GE(Sci->FreeListInfo.PoppedBlocks, Sci->FreeListInfo.PushedBlocks);
const uptr BytesInFreeList = const uptr BytesInFreeList =
@ -871,6 +955,60 @@ private:
if (UNLIKELY(BytesInFreeList == 0)) if (UNLIKELY(BytesInFreeList == 0))
return 0; return 0;
// ====================================================================== //
// 1. Check if we have enough free blocks and if it's worth doing a page
// release.
// ====================================================================== //
if (ReleaseType != ReleaseToOS::ForceAll &&
!hasChanceToReleasePages(Sci, BlockSize, BytesInFreeList,
ReleaseType)) {
return 0;
}
const uptr First = Sci->MinRegionIndex;
const uptr Last = Sci->MaxRegionIndex;
DCHECK_NE(Last, 0U);
DCHECK_LE(First, Last);
uptr TotalReleasedBytes = 0;
const uptr Base = First * RegionSize;
const uptr NumberOfRegions = Last - First + 1U;
// ==================================================================== //
// 2. Mark the free blocks and we can tell which pages are in-use by
// querying `PageReleaseContext`.
// ==================================================================== //
PageReleaseContext Context = markFreeBlocks(Sci, ClassId, BlockSize, Base,
NumberOfRegions, ReleaseType);
if (!Context.hasBlockMarked())
return 0;
// ==================================================================== //
// 3. Release the unused physical pages back to the OS.
// ==================================================================== //
ReleaseRecorder Recorder(Base);
auto SkipRegion = [this, First, ClassId](uptr RegionIndex) {
ScopedLock L(ByteMapMutex);
return (PossibleRegions[First + RegionIndex] - 1U) != ClassId;
};
releaseFreeMemoryToOS(Context, Recorder, SkipRegion);
if (Recorder.getReleasedRangesCount() > 0) {
Sci->ReleaseInfo.BytesInFreeListAtLastCheckpoint = BytesInFreeList;
Sci->ReleaseInfo.RangesReleased += Recorder.getReleasedRangesCount();
Sci->ReleaseInfo.LastReleasedBytes = Recorder.getReleasedBytes();
TotalReleasedBytes += Sci->ReleaseInfo.LastReleasedBytes;
}
Sci->ReleaseInfo.LastReleaseAtNs = getMonotonicTimeFast();
return TotalReleasedBytes;
}
bool hasChanceToReleasePages(SizeClassInfo *Sci, uptr BlockSize,
uptr BytesInFreeList, ReleaseToOS ReleaseType)
REQUIRES(Sci->Mutex) {
DCHECK_GE(Sci->FreeListInfo.PoppedBlocks, Sci->FreeListInfo.PushedBlocks);
const uptr PageSize = getPageSizeCached();
if (BytesInFreeList <= Sci->ReleaseInfo.BytesInFreeListAtLastCheckpoint) if (BytesInFreeList <= Sci->ReleaseInfo.BytesInFreeListAtLastCheckpoint)
Sci->ReleaseInfo.BytesInFreeListAtLastCheckpoint = BytesInFreeList; Sci->ReleaseInfo.BytesInFreeListAtLastCheckpoint = BytesInFreeList;
@ -892,22 +1030,20 @@ private:
// (BytesInFreeListAtLastCheckpoint - BytesInFreeList). // (BytesInFreeListAtLastCheckpoint - BytesInFreeList).
const uptr PushedBytesDelta = const uptr PushedBytesDelta =
BytesInFreeList - Sci->ReleaseInfo.BytesInFreeListAtLastCheckpoint; BytesInFreeList - Sci->ReleaseInfo.BytesInFreeListAtLastCheckpoint;
if (PushedBytesDelta < PageSize && ReleaseType != ReleaseToOS::ForceAll) if (PushedBytesDelta < PageSize)
return 0; return false;
const bool CheckDensity =
isSmallBlock(BlockSize) && ReleaseType != ReleaseToOS::ForceAll;
// Releasing smaller blocks is expensive, so we want to make sure that a // Releasing smaller blocks is expensive, so we want to make sure that a
// significant amount of bytes are free, and that there has been a good // significant amount of bytes are free, and that there has been a good
// amount of batches pushed to the freelist before attempting to release. // amount of batches pushed to the freelist before attempting to release.
if (CheckDensity && ReleaseType == ReleaseToOS::Normal) if (isSmallBlock(BlockSize) && ReleaseType == ReleaseToOS::Normal)
if (PushedBytesDelta < Sci->AllocatedUser / 16U) if (PushedBytesDelta < Sci->AllocatedUser / 16U)
return 0; return false;
if (ReleaseType == ReleaseToOS::Normal) { if (ReleaseType == ReleaseToOS::Normal) {
const s32 IntervalMs = atomic_load_relaxed(&ReleaseToOsIntervalMs); const s32 IntervalMs = atomic_load_relaxed(&ReleaseToOsIntervalMs);
if (IntervalMs < 0) if (IntervalMs < 0)
return 0; return false;
// The constant 8 here is selected from profiling some apps and the number // The constant 8 here is selected from profiling some apps and the number
// of unreleased pages in the large size classes is around 16 pages or // of unreleased pages in the large size classes is around 16 pages or
@ -920,30 +1056,31 @@ private:
static_cast<u64>(IntervalMs) * 1000000 > static_cast<u64>(IntervalMs) * 1000000 >
getMonotonicTimeFast()) { getMonotonicTimeFast()) {
// Memory was returned recently. // Memory was returned recently.
return 0; return false;
} }
} }
} // if (ReleaseType == ReleaseToOS::Normal) } // if (ReleaseType == ReleaseToOS::Normal)
const uptr First = Sci->MinRegionIndex; return true;
const uptr Last = Sci->MaxRegionIndex; }
DCHECK_NE(Last, 0U);
DCHECK_LE(First, Last); PageReleaseContext markFreeBlocks(SizeClassInfo *Sci, const uptr ClassId,
uptr TotalReleasedBytes = 0; const uptr BlockSize, const uptr Base,
const uptr Base = First * RegionSize; const uptr NumberOfRegions,
const uptr NumberOfRegions = Last - First + 1U; ReleaseToOS ReleaseType)
const uptr GroupSize = (1U << GroupSizeLog); REQUIRES(Sci->Mutex) {
const uptr PageSize = getPageSizeCached();
const uptr GroupSize = (1UL << GroupSizeLog);
const uptr CurGroupBase = const uptr CurGroupBase =
compactPtrGroupBase(compactPtr(ClassId, Sci->CurrentRegion)); compactPtrGroupBase(compactPtr(ClassId, Sci->CurrentRegion));
ReleaseRecorder Recorder(Base);
PageReleaseContext Context(BlockSize, NumberOfRegions, PageReleaseContext Context(BlockSize, NumberOfRegions,
/*ReleaseSize=*/RegionSize); /*ReleaseSize=*/RegionSize);
auto DecompactPtr = [](CompactPtrT CompactPtr) { auto DecompactPtr = [](CompactPtrT CompactPtr) {
return reinterpret_cast<uptr>(CompactPtr); return reinterpret_cast<uptr>(CompactPtr);
}; };
for (BatchGroup &BG : Sci->FreeListInfo.BlockList) { for (BatchGroupT &BG : Sci->FreeListInfo.BlockList) {
const uptr GroupBase = decompactGroupBase(BG.CompactPtrGroupBase); const uptr GroupBase = decompactGroupBase(BG.CompactPtrGroupBase);
// The `GroupSize` may not be divided by `BlockSize`, which means there is // The `GroupSize` may not be divided by `BlockSize`, which means there is
// an unused space at the end of Region. Exclude that space to avoid // an unused space at the end of Region. Exclude that space to avoid
@ -960,25 +1097,27 @@ private:
BG.Batches.front()->getCount(); BG.Batches.front()->getCount();
const uptr BytesInBG = NumBlocks * BlockSize; const uptr BytesInBG = NumBlocks * BlockSize;
if (ReleaseType != ReleaseToOS::ForceAll && if (ReleaseType != ReleaseToOS::ForceAll) {
BytesInBG <= BG.BytesInBGAtLastCheckpoint) { if (BytesInBG <= BG.BytesInBGAtLastCheckpoint) {
BG.BytesInBGAtLastCheckpoint = BytesInBG; BG.BytesInBGAtLastCheckpoint = BytesInBG;
continue; continue;
} }
const uptr PushedBytesDelta = BytesInBG - BG.BytesInBGAtLastCheckpoint;
if (ReleaseType != ReleaseToOS::ForceAll && PushedBytesDelta < PageSize)
continue;
// Given the randomness property, we try to release the pages only if the const uptr PushedBytesDelta = BytesInBG - BG.BytesInBGAtLastCheckpoint;
// bytes used by free blocks exceed certain proportion of allocated if (PushedBytesDelta < PageSize)
// spaces. continue;
if (CheckDensity && (BytesInBG * 100U) / AllocatedGroupSize <
(100U - 1U - BlockSize / 16U)) { // Given the randomness property, we try to release the pages only if
continue; // the bytes used by free blocks exceed certain proportion of allocated
// spaces.
if (isSmallBlock(BlockSize) && (BytesInBG * 100U) / AllocatedGroupSize <
(100U - 1U - BlockSize / 16U)) {
continue;
}
} }
// TODO: Consider updating this after page release if `ReleaseRecorder` // TODO: Consider updating this after page release if `ReleaseRecorder`
// can tell the releasd bytes in each group. // can tell the released bytes in each group.
BG.BytesInBGAtLastCheckpoint = BytesInBG; BG.BytesInBGAtLastCheckpoint = BytesInBG;
const uptr MaxContainedBlocks = AllocatedGroupSize / BlockSize; const uptr MaxContainedBlocks = AllocatedGroupSize / BlockSize;
@ -1006,27 +1145,10 @@ private:
// We may not be able to do the page release In a rare case that we may // We may not be able to do the page release In a rare case that we may
// fail on PageMap allocation. // fail on PageMap allocation.
if (UNLIKELY(!Context.hasBlockMarked())) if (UNLIKELY(!Context.hasBlockMarked()))
return 0; break;
} }
if (!Context.hasBlockMarked()) return Context;
return 0;
auto SkipRegion = [this, First, ClassId](uptr RegionIndex) {
ScopedLock L(ByteMapMutex);
return (PossibleRegions[First + RegionIndex] - 1U) != ClassId;
};
releaseFreeMemoryToOS(Context, Recorder, SkipRegion);
if (Recorder.getReleasedRangesCount() > 0) {
Sci->ReleaseInfo.BytesInFreeListAtLastCheckpoint = BytesInFreeList;
Sci->ReleaseInfo.RangesReleased += Recorder.getReleasedRangesCount();
Sci->ReleaseInfo.LastReleasedBytes = Recorder.getReleasedBytes();
TotalReleasedBytes += Sci->ReleaseInfo.LastReleasedBytes;
}
Sci->ReleaseInfo.LastReleaseAtNs = getMonotonicTimeFast();
return TotalReleasedBytes;
} }
SizeClassInfo SizeClassInfoArray[NumClasses] = {}; SizeClassInfo SizeClassInfoArray[NumClasses] = {};

View file

@ -9,6 +9,7 @@
#ifndef SCUDO_PRIMARY64_H_ #ifndef SCUDO_PRIMARY64_H_
#define SCUDO_PRIMARY64_H_ #define SCUDO_PRIMARY64_H_
#include "allocator_common.h"
#include "bytemap.h" #include "bytemap.h"
#include "common.h" #include "common.h"
#include "list.h" #include "list.h"
@ -21,6 +22,8 @@
#include "string_utils.h" #include "string_utils.h"
#include "thread_annotations.h" #include "thread_annotations.h"
#include "condition_variable.h"
namespace scudo { namespace scudo {
// SizeClassAllocator64 is an allocator tuned for 64-bit address space. // SizeClassAllocator64 is an allocator tuned for 64-bit address space.
@ -47,27 +50,39 @@ template <typename Config> class SizeClassAllocator64 {
public: public:
typedef typename Config::Primary::CompactPtrT CompactPtrT; typedef typename Config::Primary::CompactPtrT CompactPtrT;
typedef typename Config::Primary::SizeClassMap SizeClassMap; typedef typename Config::Primary::SizeClassMap SizeClassMap;
typedef typename ConditionVariableState<
typename Config::Primary>::ConditionVariableT ConditionVariableT;
static const uptr CompactPtrScale = Config::Primary::CompactPtrScale; static const uptr CompactPtrScale = Config::Primary::CompactPtrScale;
static const uptr RegionSizeLog = Config::Primary::RegionSizeLog;
static const uptr GroupSizeLog = Config::Primary::GroupSizeLog; static const uptr GroupSizeLog = Config::Primary::GroupSizeLog;
static_assert(RegionSizeLog >= GroupSizeLog,
"Group size shouldn't be greater than the region size");
static const uptr GroupScale = GroupSizeLog - CompactPtrScale; static const uptr GroupScale = GroupSizeLog - CompactPtrScale;
typedef SizeClassAllocator64<Config> ThisT; typedef SizeClassAllocator64<Config> ThisT;
typedef SizeClassAllocatorLocalCache<ThisT> CacheT; typedef SizeClassAllocatorLocalCache<ThisT> CacheT;
typedef typename CacheT::TransferBatch TransferBatch; typedef TransferBatch<ThisT> TransferBatchT;
typedef typename CacheT::BatchGroup BatchGroup; typedef BatchGroup<ThisT> BatchGroupT;
static_assert(sizeof(BatchGroupT) <= sizeof(TransferBatchT),
"BatchGroupT uses the same class size as TransferBatchT");
static uptr getSizeByClassId(uptr ClassId) { static uptr getSizeByClassId(uptr ClassId) {
return (ClassId == SizeClassMap::BatchClassId) return (ClassId == SizeClassMap::BatchClassId)
? roundUp(sizeof(TransferBatch), 1U << CompactPtrScale) ? roundUp(sizeof(TransferBatchT), 1U << CompactPtrScale)
: SizeClassMap::getSizeByClassId(ClassId); : SizeClassMap::getSizeByClassId(ClassId);
} }
static bool canAllocate(uptr Size) { return Size <= SizeClassMap::MaxSize; } static bool canAllocate(uptr Size) { return Size <= SizeClassMap::MaxSize; }
static bool conditionVariableEnabled() {
return ConditionVariableState<typename Config::Primary>::enabled();
}
void init(s32 ReleaseToOsInterval) NO_THREAD_SAFETY_ANALYSIS { void init(s32 ReleaseToOsInterval) NO_THREAD_SAFETY_ANALYSIS {
DCHECK(isAligned(reinterpret_cast<uptr>(this), alignof(ThisT))); DCHECK(isAligned(reinterpret_cast<uptr>(this), alignof(ThisT)));
const uptr PageSize = getPageSizeCached(); const uptr PageSize = getPageSizeCached();
const uptr GroupSize = (1U << GroupSizeLog); const uptr GroupSize = (1UL << GroupSizeLog);
const uptr PagesInGroup = GroupSize / PageSize; const uptr PagesInGroup = GroupSize / PageSize;
const uptr MinSizeClass = getSizeByClassId(1); const uptr MinSizeClass = getSizeByClassId(1);
// When trying to release pages back to memory, visiting smaller size // When trying to release pages back to memory, visiting smaller size
@ -117,13 +132,13 @@ public:
for (uptr I = 0; I < NumClasses; I++) { for (uptr I = 0; I < NumClasses; I++) {
RegionInfo *Region = getRegionInfo(I); RegionInfo *Region = getRegionInfo(I);
// The actual start of a region is offset by a random number of pages // The actual start of a region is offset by a random number of pages
// when PrimaryEnableRandomOffset is set. // when PrimaryEnableRandomOffset is set.
Region->RegionBeg = Region->RegionBeg = (PrimaryBase + (I << RegionSizeLog)) +
(PrimaryBase + (I << Config::Primary::RegionSizeLog)) + (Config::Primary::EnableRandomOffset
(Config::Primary::EnableRandomOffset ? ((getRandomModN(&Seed, 16) + 1) * PageSize)
? ((getRandomModN(&Seed, 16) + 1) * PageSize) : 0);
: 0);
Region->RandState = getRandomU32(&Seed); Region->RandState = getRandomU32(&Seed);
// Releasing small blocks is expensive, set a higher threshold to avoid // Releasing small blocks is expensive, set a higher threshold to avoid
// frequent page releases. // frequent page releases.
@ -134,11 +149,16 @@ public:
Region->ReleaseInfo.LastReleaseAtNs = Time; Region->ReleaseInfo.LastReleaseAtNs = Time;
Region->MemMapInfo.MemMap = ReservedMemory.dispatch( Region->MemMapInfo.MemMap = ReservedMemory.dispatch(
PrimaryBase + (I << Config::Primary::RegionSizeLog), RegionSize); PrimaryBase + (I << RegionSizeLog), RegionSize);
CHECK(Region->MemMapInfo.MemMap.isAllocated()); CHECK(Region->MemMapInfo.MemMap.isAllocated());
} }
shuffle(RegionInfoArray, NumClasses, &Seed); shuffle(RegionInfoArray, NumClasses, &Seed);
// The binding should be done after region shuffling so that it won't bind
// the FLLock from the wrong region.
for (uptr I = 0; I < NumClasses; I++)
getRegionInfo(I)->FLLockCV.bindTestOnly(getRegionInfo(I)->FLLock);
setOption(Option::ReleaseInterval, static_cast<sptr>(ReleaseToOsInterval)); setOption(Option::ReleaseInterval, static_cast<sptr>(ReleaseToOsInterval));
} }
@ -165,7 +185,7 @@ public:
ScopedLock FL(Region->FLLock); ScopedLock FL(Region->FLLock);
const uptr BlockSize = getSizeByClassId(I); const uptr BlockSize = getSizeByClassId(I);
uptr TotalBlocks = 0; uptr TotalBlocks = 0;
for (BatchGroup &BG : Region->FreeListInfo.BlockList) { for (BatchGroupT &BG : Region->FreeListInfo.BlockList) {
// `BG::Batches` are `TransferBatches`. +1 for `BatchGroup`. // `BG::Batches` are `TransferBatches`. +1 for `BatchGroup`.
BatchClassUsedInFreeLists += BG.Batches.size() + 1; BatchClassUsedInFreeLists += BG.Batches.size() + 1;
for (const auto &It : BG.Batches) for (const auto &It : BG.Batches)
@ -182,7 +202,7 @@ public:
ScopedLock FL(Region->FLLock); ScopedLock FL(Region->FLLock);
const uptr BlockSize = getSizeByClassId(SizeClassMap::BatchClassId); const uptr BlockSize = getSizeByClassId(SizeClassMap::BatchClassId);
uptr TotalBlocks = 0; uptr TotalBlocks = 0;
for (BatchGroup &BG : Region->FreeListInfo.BlockList) { for (BatchGroupT &BG : Region->FreeListInfo.BlockList) {
if (LIKELY(!BG.Batches.empty())) { if (LIKELY(!BG.Batches.empty())) {
for (const auto &It : BG.Batches) for (const auto &It : BG.Batches)
TotalBlocks += It.getCount(); TotalBlocks += It.getCount();
@ -201,51 +221,64 @@ public:
DCHECK_EQ(BlocksInUse, BatchClassUsedInFreeLists); DCHECK_EQ(BlocksInUse, BatchClassUsedInFreeLists);
} }
TransferBatch *popBatch(CacheT *C, uptr ClassId) { // Note that the `MaxBlockCount` will be used when we support arbitrary blocks
// count. Now it's the same as the number of blocks stored in the
// `TransferBatch`.
u16 popBlocks(CacheT *C, uptr ClassId, CompactPtrT *ToArray,
UNUSED const u16 MaxBlockCount) {
TransferBatchT *B = popBatch(C, ClassId);
if (!B)
return 0;
const u16 Count = B->getCount();
DCHECK_GT(Count, 0U);
B->moveToArray(ToArray);
if (ClassId != SizeClassMap::BatchClassId)
C->deallocate(SizeClassMap::BatchClassId, B);
return Count;
}
TransferBatchT *popBatch(CacheT *C, uptr ClassId) {
DCHECK_LT(ClassId, NumClasses); DCHECK_LT(ClassId, NumClasses);
RegionInfo *Region = getRegionInfo(ClassId); RegionInfo *Region = getRegionInfo(ClassId);
{ {
ScopedLock L(Region->FLLock); ScopedLock L(Region->FLLock);
TransferBatch *B = popBatchImpl(C, ClassId, Region); TransferBatchT *B = popBatchImpl(C, ClassId, Region);
if (LIKELY(B)) if (LIKELY(B))
return B; return B;
} }
bool PrintStats = false; bool ReportRegionExhausted = false;
TransferBatch *B = nullptr; TransferBatchT *B = nullptr;
while (true) { if (conditionVariableEnabled()) {
// When two threads compete for `Region->MMLock`, we only want one of them B = popBatchWithCV(C, ClassId, Region, ReportRegionExhausted);
// does the populateFreeListAndPopBatch(). To avoid both of them doing } else {
// that, always check the freelist before mapping new pages. while (true) {
// // When two threads compete for `Region->MMLock`, we only want one of
// TODO(chiahungduan): Use a condition variable so that we don't need to // them to call populateFreeListAndPopBatch(). To avoid both of them
// hold `Region->MMLock` here. // doing that, always check the freelist before mapping new pages.
ScopedLock ML(Region->MMLock); ScopedLock ML(Region->MMLock);
{ {
ScopedLock FL(Region->FLLock); ScopedLock FL(Region->FLLock);
B = popBatchImpl(C, ClassId, Region); if ((B = popBatchImpl(C, ClassId, Region)))
if (LIKELY(B)) break;
return B; }
const bool RegionIsExhausted = Region->Exhausted;
if (!RegionIsExhausted)
B = populateFreeListAndPopBatch(C, ClassId, Region);
ReportRegionExhausted = !RegionIsExhausted && Region->Exhausted;
break;
} }
const bool RegionIsExhausted = Region->Exhausted;
if (!RegionIsExhausted)
B = populateFreeListAndPopBatch(C, ClassId, Region);
PrintStats = !RegionIsExhausted && Region->Exhausted;
break;
} }
// Note that `getStats()` requires locking each region so we can't call it if (UNLIKELY(ReportRegionExhausted)) {
// while locking the Region->Mutex in the above. Printf("Can't populate more pages for size class %zu.\n",
if (UNLIKELY(PrintStats)) { getSizeByClassId(ClassId));
ScopedString Str;
getStats(&Str);
Str.append(
"Scudo OOM: The process has exhausted %zuM for size class %zu.\n",
RegionSize >> 20, getSizeByClassId(ClassId));
Str.output();
// Theoretically, BatchClass shouldn't be used up. Abort immediately when // Theoretically, BatchClass shouldn't be used up. Abort immediately when
// it happens. // it happens.
@ -265,30 +298,36 @@ public:
if (ClassId == SizeClassMap::BatchClassId) { if (ClassId == SizeClassMap::BatchClassId) {
ScopedLock L(Region->FLLock); ScopedLock L(Region->FLLock);
pushBatchClassBlocks(Region, Array, Size); pushBatchClassBlocks(Region, Array, Size);
if (conditionVariableEnabled())
Region->FLLockCV.notifyAll(Region->FLLock);
return; return;
} }
// TODO(chiahungduan): Consider not doing grouping if the group size is not // TODO(chiahungduan): Consider not doing grouping if the group size is not
// greater than the block size with a certain scale. // greater than the block size with a certain scale.
// Sort the blocks so that blocks belonging to the same group can be pushed
// together.
bool SameGroup = true; bool SameGroup = true;
for (u32 I = 1; I < Size; ++I) { if (GroupSizeLog < RegionSizeLog) {
if (compactPtrGroup(Array[I - 1]) != compactPtrGroup(Array[I])) // Sort the blocks so that blocks belonging to the same group can be
SameGroup = false; // pushed together.
CompactPtrT Cur = Array[I]; for (u32 I = 1; I < Size; ++I) {
u32 J = I; if (compactPtrGroup(Array[I - 1]) != compactPtrGroup(Array[I]))
while (J > 0 && compactPtrGroup(Cur) < compactPtrGroup(Array[J - 1])) { SameGroup = false;
Array[J] = Array[J - 1]; CompactPtrT Cur = Array[I];
--J; u32 J = I;
while (J > 0 && compactPtrGroup(Cur) < compactPtrGroup(Array[J - 1])) {
Array[J] = Array[J - 1];
--J;
}
Array[J] = Cur;
} }
Array[J] = Cur;
} }
{ {
ScopedLock L(Region->FLLock); ScopedLock L(Region->FLLock);
pushBlocksImpl(C, ClassId, Region, Array, Size, SameGroup); pushBlocksImpl(C, ClassId, Region, Array, Size, SameGroup);
if (conditionVariableEnabled())
Region->FLLockCV.notifyAll(Region->FLLock);
} }
} }
@ -363,6 +402,18 @@ public:
} }
} }
void getFragmentationInfo(ScopedString *Str) {
Str->append(
"Fragmentation Stats: SizeClassAllocator64: page size = %zu bytes\n",
getPageSizeCached());
for (uptr I = 1; I < NumClasses; I++) {
RegionInfo *Region = getRegionInfo(I);
ScopedLock L(Region->MMLock);
getRegionFragmentationInfo(Region, I, Str);
}
}
bool setOption(Option O, sptr Value) { bool setOption(Option O, sptr Value) {
if (O == Option::ReleaseInterval) { if (O == Option::ReleaseInterval) {
const s32 Interval = Max(Min(static_cast<s32>(Value), const s32 Interval = Max(Min(static_cast<s32>(Value),
@ -477,7 +528,7 @@ public:
AtomicOptions Options; AtomicOptions Options;
private: private:
static const uptr RegionSize = 1UL << Config::Primary::RegionSizeLog; static const uptr RegionSize = 1UL << RegionSizeLog;
static const uptr NumClasses = SizeClassMap::NumClasses; static const uptr NumClasses = SizeClassMap::NumClasses;
static const uptr PrimarySize = RegionSize * NumClasses; static const uptr PrimarySize = RegionSize * NumClasses;
@ -493,7 +544,7 @@ private:
}; };
struct BlocksInfo { struct BlocksInfo {
SinglyLinkedList<BatchGroup> BlockList = {}; SinglyLinkedList<BatchGroupT> BlockList = {};
uptr PoppedBlocks = 0; uptr PoppedBlocks = 0;
uptr PushedBlocks = 0; uptr PushedBlocks = 0;
}; };
@ -509,6 +560,7 @@ private:
struct UnpaddedRegionInfo { struct UnpaddedRegionInfo {
// Mutex for operations on freelist // Mutex for operations on freelist
HybridMutex FLLock; HybridMutex FLLock;
ConditionVariableT FLLockCV GUARDED_BY(FLLock);
// Mutex for memmap operations // Mutex for memmap operations
HybridMutex MMLock ACQUIRED_BEFORE(FLLock); HybridMutex MMLock ACQUIRED_BEFORE(FLLock);
// `RegionBeg` is initialized before thread creation and won't be changed. // `RegionBeg` is initialized before thread creation and won't be changed.
@ -520,6 +572,7 @@ private:
uptr TryReleaseThreshold GUARDED_BY(MMLock) = 0; uptr TryReleaseThreshold GUARDED_BY(MMLock) = 0;
ReleaseToOsInfo ReleaseInfo GUARDED_BY(MMLock) = {}; ReleaseToOsInfo ReleaseInfo GUARDED_BY(MMLock) = {};
bool Exhausted GUARDED_BY(MMLock) = false; bool Exhausted GUARDED_BY(MMLock) = false;
bool isPopulatingFreeList GUARDED_BY(FLLock) = false;
}; };
struct RegionInfo : UnpaddedRegionInfo { struct RegionInfo : UnpaddedRegionInfo {
char Padding[SCUDO_CACHE_LINE_SIZE - char Padding[SCUDO_CACHE_LINE_SIZE -
@ -605,11 +658,11 @@ private:
// reusable and don't need additional space for them. // reusable and don't need additional space for them.
Region->FreeListInfo.PushedBlocks += Size; Region->FreeListInfo.PushedBlocks += Size;
BatchGroup *BG = Region->FreeListInfo.BlockList.front(); BatchGroupT *BG = Region->FreeListInfo.BlockList.front();
if (BG == nullptr) { if (BG == nullptr) {
// Construct `BatchGroup` on the last element. // Construct `BatchGroup` on the last element.
BG = reinterpret_cast<BatchGroup *>( BG = reinterpret_cast<BatchGroupT *>(
decompactPtr(SizeClassMap::BatchClassId, Array[Size - 1])); decompactPtr(SizeClassMap::BatchClassId, Array[Size - 1]));
--Size; --Size;
BG->Batches.clear(); BG->Batches.clear();
@ -620,8 +673,8 @@ private:
// from `CreateGroup` in `pushBlocksImpl` // from `CreateGroup` in `pushBlocksImpl`
BG->PushedBlocks = 1; BG->PushedBlocks = 1;
BG->BytesInBGAtLastCheckpoint = 0; BG->BytesInBGAtLastCheckpoint = 0;
BG->MaxCachedPerBatch = TransferBatch::getMaxCached( BG->MaxCachedPerBatch =
getSizeByClassId(SizeClassMap::BatchClassId)); CacheT::getMaxCached(getSizeByClassId(SizeClassMap::BatchClassId));
Region->FreeListInfo.BlockList.push_front(BG); Region->FreeListInfo.BlockList.push_front(BG);
} }
@ -634,7 +687,7 @@ private:
// 2. Only 1 block is pushed when the freelist is empty. // 2. Only 1 block is pushed when the freelist is empty.
if (BG->Batches.empty()) { if (BG->Batches.empty()) {
// Construct the `TransferBatch` on the last element. // Construct the `TransferBatch` on the last element.
TransferBatch *TB = reinterpret_cast<TransferBatch *>( TransferBatchT *TB = reinterpret_cast<TransferBatchT *>(
decompactPtr(SizeClassMap::BatchClassId, Array[Size - 1])); decompactPtr(SizeClassMap::BatchClassId, Array[Size - 1]));
TB->clear(); TB->clear();
// As mentioned above, addresses of `TransferBatch` and `BatchGroup` are // As mentioned above, addresses of `TransferBatch` and `BatchGroup` are
@ -649,14 +702,14 @@ private:
BG->Batches.push_front(TB); BG->Batches.push_front(TB);
} }
TransferBatch *CurBatch = BG->Batches.front(); TransferBatchT *CurBatch = BG->Batches.front();
DCHECK_NE(CurBatch, nullptr); DCHECK_NE(CurBatch, nullptr);
for (u32 I = 0; I < Size;) { for (u32 I = 0; I < Size;) {
u16 UnusedSlots = u16 UnusedSlots =
static_cast<u16>(BG->MaxCachedPerBatch - CurBatch->getCount()); static_cast<u16>(BG->MaxCachedPerBatch - CurBatch->getCount());
if (UnusedSlots == 0) { if (UnusedSlots == 0) {
CurBatch = reinterpret_cast<TransferBatch *>( CurBatch = reinterpret_cast<TransferBatchT *>(
decompactPtr(SizeClassMap::BatchClassId, Array[I])); decompactPtr(SizeClassMap::BatchClassId, Array[I]));
CurBatch->clear(); CurBatch->clear();
// Self-contained // Self-contained
@ -699,24 +752,25 @@ private:
DCHECK_GT(Size, 0U); DCHECK_GT(Size, 0U);
auto CreateGroup = [&](uptr CompactPtrGroupBase) { auto CreateGroup = [&](uptr CompactPtrGroupBase) {
BatchGroup *BG = C->createGroup(); BatchGroupT *BG =
reinterpret_cast<BatchGroupT *>(C->getBatchClassBlock());
BG->Batches.clear(); BG->Batches.clear();
TransferBatch *TB = C->createBatch(ClassId, nullptr); TransferBatchT *TB =
reinterpret_cast<TransferBatchT *>(C->getBatchClassBlock());
TB->clear(); TB->clear();
BG->CompactPtrGroupBase = CompactPtrGroupBase; BG->CompactPtrGroupBase = CompactPtrGroupBase;
BG->Batches.push_front(TB); BG->Batches.push_front(TB);
BG->PushedBlocks = 0; BG->PushedBlocks = 0;
BG->BytesInBGAtLastCheckpoint = 0; BG->BytesInBGAtLastCheckpoint = 0;
BG->MaxCachedPerBatch = BG->MaxCachedPerBatch = CacheT::getMaxCached(getSizeByClassId(ClassId));
TransferBatch::getMaxCached(getSizeByClassId(ClassId));
return BG; return BG;
}; };
auto InsertBlocks = [&](BatchGroup *BG, CompactPtrT *Array, u32 Size) { auto InsertBlocks = [&](BatchGroupT *BG, CompactPtrT *Array, u32 Size) {
SinglyLinkedList<TransferBatch> &Batches = BG->Batches; SinglyLinkedList<TransferBatchT> &Batches = BG->Batches;
TransferBatch *CurBatch = Batches.front(); TransferBatchT *CurBatch = Batches.front();
DCHECK_NE(CurBatch, nullptr); DCHECK_NE(CurBatch, nullptr);
for (u32 I = 0; I < Size;) { for (u32 I = 0; I < Size;) {
@ -724,9 +778,8 @@ private:
u16 UnusedSlots = u16 UnusedSlots =
static_cast<u16>(BG->MaxCachedPerBatch - CurBatch->getCount()); static_cast<u16>(BG->MaxCachedPerBatch - CurBatch->getCount());
if (UnusedSlots == 0) { if (UnusedSlots == 0) {
CurBatch = C->createBatch( CurBatch =
ClassId, reinterpret_cast<TransferBatchT *>(C->getBatchClassBlock());
reinterpret_cast<void *>(decompactPtr(ClassId, Array[I])));
CurBatch->clear(); CurBatch->clear();
Batches.push_front(CurBatch); Batches.push_front(CurBatch);
UnusedSlots = BG->MaxCachedPerBatch; UnusedSlots = BG->MaxCachedPerBatch;
@ -741,21 +794,11 @@ private:
}; };
Region->FreeListInfo.PushedBlocks += Size; Region->FreeListInfo.PushedBlocks += Size;
BatchGroup *Cur = Region->FreeListInfo.BlockList.front(); BatchGroupT *Cur = Region->FreeListInfo.BlockList.front();
if (ClassId == SizeClassMap::BatchClassId) {
if (Cur == nullptr) {
// Don't need to classify BatchClassId.
Cur = CreateGroup(/*CompactPtrGroupBase=*/0);
Region->FreeListInfo.BlockList.push_front(Cur);
}
InsertBlocks(Cur, Array, Size);
return;
}
// In the following, `Cur` always points to the BatchGroup for blocks that // In the following, `Cur` always points to the BatchGroup for blocks that
// will be pushed next. `Prev` is the element right before `Cur`. // will be pushed next. `Prev` is the element right before `Cur`.
BatchGroup *Prev = nullptr; BatchGroupT *Prev = nullptr;
while (Cur != nullptr && while (Cur != nullptr &&
compactPtrGroup(Array[0]) > Cur->CompactPtrGroupBase) { compactPtrGroup(Array[0]) > Cur->CompactPtrGroupBase) {
@ -812,26 +855,96 @@ private:
InsertBlocks(Cur, Array + Size - Count, Count); InsertBlocks(Cur, Array + Size - Count, Count);
} }
TransferBatchT *popBatchWithCV(CacheT *C, uptr ClassId, RegionInfo *Region,
bool &ReportRegionExhausted) {
TransferBatchT *B = nullptr;
while (true) {
// We only expect one thread doing the freelist refillment and other
// threads will be waiting for either the completion of the
// `populateFreeListAndPopBatch()` or `pushBlocks()` called by other
// threads.
bool PopulateFreeList = false;
{
ScopedLock FL(Region->FLLock);
if (!Region->isPopulatingFreeList) {
Region->isPopulatingFreeList = true;
PopulateFreeList = true;
}
}
if (PopulateFreeList) {
ScopedLock ML(Region->MMLock);
const bool RegionIsExhausted = Region->Exhausted;
if (!RegionIsExhausted)
B = populateFreeListAndPopBatch(C, ClassId, Region);
ReportRegionExhausted = !RegionIsExhausted && Region->Exhausted;
{
// Before reacquiring the `FLLock`, the freelist may be used up again
// and some threads are waiting for the freelist refillment by the
// current thread. It's important to set
// `Region->isPopulatingFreeList` to false so the threads about to
// sleep will notice the status change.
ScopedLock FL(Region->FLLock);
Region->isPopulatingFreeList = false;
Region->FLLockCV.notifyAll(Region->FLLock);
}
break;
}
// At here, there are two preconditions to be met before waiting,
// 1. The freelist is empty.
// 2. Region->isPopulatingFreeList == true, i.e, someone is still doing
// `populateFreeListAndPopBatch()`.
//
// Note that it has the chance that freelist is empty but
// Region->isPopulatingFreeList == false because all the new populated
// blocks were used up right after the refillment. Therefore, we have to
// check if someone is still populating the freelist.
ScopedLock FL(Region->FLLock);
if (LIKELY(B = popBatchImpl(C, ClassId, Region)))
break;
if (!Region->isPopulatingFreeList)
continue;
// Now the freelist is empty and someone's doing the refillment. We will
// wait until anyone refills the freelist or someone finishes doing
// `populateFreeListAndPopBatch()`. The refillment can be done by
// `populateFreeListAndPopBatch()`, `pushBlocks()`,
// `pushBatchClassBlocks()` and `mergeGroupsToReleaseBack()`.
Region->FLLockCV.wait(Region->FLLock);
if (LIKELY(B = popBatchImpl(C, ClassId, Region)))
break;
}
return B;
}
// Pop one TransferBatch from a BatchGroup. The BatchGroup with the smallest // Pop one TransferBatch from a BatchGroup. The BatchGroup with the smallest
// group id will be considered first. // group id will be considered first.
// //
// The region mutex needs to be held while calling this method. // The region mutex needs to be held while calling this method.
TransferBatch *popBatchImpl(CacheT *C, uptr ClassId, RegionInfo *Region) TransferBatchT *popBatchImpl(CacheT *C, uptr ClassId, RegionInfo *Region)
REQUIRES(Region->FLLock) { REQUIRES(Region->FLLock) {
if (Region->FreeListInfo.BlockList.empty()) if (Region->FreeListInfo.BlockList.empty())
return nullptr; return nullptr;
SinglyLinkedList<TransferBatch> &Batches = SinglyLinkedList<TransferBatchT> &Batches =
Region->FreeListInfo.BlockList.front()->Batches; Region->FreeListInfo.BlockList.front()->Batches;
if (Batches.empty()) { if (Batches.empty()) {
DCHECK_EQ(ClassId, SizeClassMap::BatchClassId); DCHECK_EQ(ClassId, SizeClassMap::BatchClassId);
BatchGroup *BG = Region->FreeListInfo.BlockList.front(); BatchGroupT *BG = Region->FreeListInfo.BlockList.front();
Region->FreeListInfo.BlockList.pop_front(); Region->FreeListInfo.BlockList.pop_front();
// Block used by `BatchGroup` is from BatchClassId. Turn the block into // Block used by `BatchGroup` is from BatchClassId. Turn the block into
// `TransferBatch` with single block. // `TransferBatch` with single block.
TransferBatch *TB = reinterpret_cast<TransferBatch *>(BG); TransferBatchT *TB = reinterpret_cast<TransferBatchT *>(BG);
TB->clear(); TB->clear();
TB->add( TB->add(
compactPtr(SizeClassMap::BatchClassId, reinterpret_cast<uptr>(TB))); compactPtr(SizeClassMap::BatchClassId, reinterpret_cast<uptr>(TB)));
@ -839,13 +952,13 @@ private:
return TB; return TB;
} }
TransferBatch *B = Batches.front(); TransferBatchT *B = Batches.front();
Batches.pop_front(); Batches.pop_front();
DCHECK_NE(B, nullptr); DCHECK_NE(B, nullptr);
DCHECK_GT(B->getCount(), 0U); DCHECK_GT(B->getCount(), 0U);
if (Batches.empty()) { if (Batches.empty()) {
BatchGroup *BG = Region->FreeListInfo.BlockList.front(); BatchGroupT *BG = Region->FreeListInfo.BlockList.front();
Region->FreeListInfo.BlockList.pop_front(); Region->FreeListInfo.BlockList.pop_front();
// We don't keep BatchGroup with zero blocks to avoid empty-checking while // We don't keep BatchGroup with zero blocks to avoid empty-checking while
@ -863,11 +976,11 @@ private:
} }
// Refill the freelist and return one batch. // Refill the freelist and return one batch.
NOINLINE TransferBatch *populateFreeListAndPopBatch(CacheT *C, uptr ClassId, NOINLINE TransferBatchT *populateFreeListAndPopBatch(CacheT *C, uptr ClassId,
RegionInfo *Region) RegionInfo *Region)
REQUIRES(Region->MMLock) EXCLUDES(Region->FLLock) { REQUIRES(Region->MMLock) EXCLUDES(Region->FLLock) {
const uptr Size = getSizeByClassId(ClassId); const uptr Size = getSizeByClassId(ClassId);
const u16 MaxCount = TransferBatch::getMaxCached(Size); const u16 MaxCount = CacheT::getMaxCached(Size);
const uptr RegionBeg = Region->RegionBeg; const uptr RegionBeg = Region->RegionBeg;
const uptr MappedUser = Region->MemMapInfo.MappedUser; const uptr MappedUser = Region->MemMapInfo.MappedUser;
@ -903,7 +1016,7 @@ private:
DCHECK_GT(NumberOfBlocks, 0); DCHECK_GT(NumberOfBlocks, 0);
constexpr u32 ShuffleArraySize = constexpr u32 ShuffleArraySize =
MaxNumBatches * TransferBatch::MaxNumCached; MaxNumBatches * TransferBatchT::MaxNumCached;
CompactPtrT ShuffleArray[ShuffleArraySize]; CompactPtrT ShuffleArray[ShuffleArraySize];
DCHECK_LE(NumberOfBlocks, ShuffleArraySize); DCHECK_LE(NumberOfBlocks, ShuffleArraySize);
@ -936,7 +1049,7 @@ private:
pushBatchClassBlocks(Region, ShuffleArray, NumberOfBlocks); pushBatchClassBlocks(Region, ShuffleArray, NumberOfBlocks);
} }
TransferBatch *B = popBatchImpl(C, ClassId, Region); TransferBatchT *B = popBatchImpl(C, ClassId, Region);
DCHECK_NE(B, nullptr); DCHECK_NE(B, nullptr);
// Note that `PushedBlocks` and `PoppedBlocks` are supposed to only record // Note that `PushedBlocks` and `PoppedBlocks` are supposed to only record
@ -957,10 +1070,10 @@ private:
if (Region->MemMapInfo.MappedUser == 0) if (Region->MemMapInfo.MappedUser == 0)
return; return;
const uptr BlockSize = getSizeByClassId(ClassId); const uptr BlockSize = getSizeByClassId(ClassId);
const uptr InUse = const uptr InUseBlocks =
Region->FreeListInfo.PoppedBlocks - Region->FreeListInfo.PushedBlocks; Region->FreeListInfo.PoppedBlocks - Region->FreeListInfo.PushedBlocks;
const uptr BytesInFreeList = const uptr BytesInFreeList =
Region->MemMapInfo.AllocatedUser - InUse * BlockSize; Region->MemMapInfo.AllocatedUser - InUseBlocks * BlockSize;
uptr RegionPushedBytesDelta = 0; uptr RegionPushedBytesDelta = 0;
if (BytesInFreeList >= if (BytesInFreeList >=
Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint) { Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint) {
@ -972,122 +1085,145 @@ private:
"%s %02zu (%6zu): mapped: %6zuK popped: %7zu pushed: %7zu " "%s %02zu (%6zu): mapped: %6zuK popped: %7zu pushed: %7zu "
"inuse: %6zu total: %6zu releases: %6zu last " "inuse: %6zu total: %6zu releases: %6zu last "
"released: %6zuK latest pushed bytes: %6zuK region: 0x%zx (0x%zx)\n", "released: %6zuK latest pushed bytes: %6zuK region: 0x%zx (0x%zx)\n",
Region->Exhausted ? "F" : " ", ClassId, getSizeByClassId(ClassId), Region->Exhausted ? "E" : " ", ClassId, getSizeByClassId(ClassId),
Region->MemMapInfo.MappedUser >> 10, Region->FreeListInfo.PoppedBlocks, Region->MemMapInfo.MappedUser >> 10, Region->FreeListInfo.PoppedBlocks,
Region->FreeListInfo.PushedBlocks, InUse, TotalChunks, Region->FreeListInfo.PushedBlocks, InUseBlocks, TotalChunks,
Region->ReleaseInfo.RangesReleased, Region->ReleaseInfo.RangesReleased,
Region->ReleaseInfo.LastReleasedBytes >> 10, Region->ReleaseInfo.LastReleasedBytes >> 10,
RegionPushedBytesDelta >> 10, Region->RegionBeg, RegionPushedBytesDelta >> 10, Region->RegionBeg,
getRegionBaseByClassId(ClassId)); getRegionBaseByClassId(ClassId));
} }
void getRegionFragmentationInfo(RegionInfo *Region, uptr ClassId,
ScopedString *Str) REQUIRES(Region->MMLock) {
const uptr BlockSize = getSizeByClassId(ClassId);
const uptr AllocatedUserEnd =
Region->MemMapInfo.AllocatedUser + Region->RegionBeg;
SinglyLinkedList<BatchGroupT> GroupsToRelease;
{
ScopedLock L(Region->FLLock);
GroupsToRelease = Region->FreeListInfo.BlockList;
Region->FreeListInfo.BlockList.clear();
}
FragmentationRecorder Recorder;
if (!GroupsToRelease.empty()) {
PageReleaseContext Context =
markFreeBlocks(Region, BlockSize, AllocatedUserEnd,
getCompactPtrBaseByClassId(ClassId), GroupsToRelease);
auto SkipRegion = [](UNUSED uptr RegionIndex) { return false; };
releaseFreeMemoryToOS(Context, Recorder, SkipRegion);
mergeGroupsToReleaseBack(Region, GroupsToRelease);
}
ScopedLock L(Region->FLLock);
const uptr PageSize = getPageSizeCached();
const uptr TotalBlocks = Region->MemMapInfo.AllocatedUser / BlockSize;
const uptr InUseBlocks =
Region->FreeListInfo.PoppedBlocks - Region->FreeListInfo.PushedBlocks;
const uptr AllocatedPagesCount =
roundUp(Region->MemMapInfo.AllocatedUser, PageSize) / PageSize;
DCHECK_GE(AllocatedPagesCount, Recorder.getReleasedPagesCount());
const uptr InUsePages =
AllocatedPagesCount - Recorder.getReleasedPagesCount();
const uptr InUseBytes = InUsePages * PageSize;
uptr Integral;
uptr Fractional;
computePercentage(BlockSize * InUseBlocks, InUsePages * PageSize, &Integral,
&Fractional);
Str->append(" %02zu (%6zu): inuse/total blocks: %6zu/%6zu inuse/total "
"pages: %6zu/%6zu inuse bytes: %6zuK util: %3zu.%02zu%%\n",
ClassId, BlockSize, InUseBlocks, TotalBlocks, InUsePages,
AllocatedPagesCount, InUseBytes >> 10, Integral, Fractional);
}
NOINLINE uptr releaseToOSMaybe(RegionInfo *Region, uptr ClassId, NOINLINE uptr releaseToOSMaybe(RegionInfo *Region, uptr ClassId,
ReleaseToOS ReleaseType = ReleaseToOS::Normal) ReleaseToOS ReleaseType = ReleaseToOS::Normal)
REQUIRES(Region->MMLock) EXCLUDES(Region->FLLock) { REQUIRES(Region->MMLock) EXCLUDES(Region->FLLock) {
ScopedLock L(Region->FLLock);
const uptr BlockSize = getSizeByClassId(ClassId); const uptr BlockSize = getSizeByClassId(ClassId);
const uptr BytesInFreeList = uptr BytesInFreeList;
Region->MemMapInfo.AllocatedUser - (Region->FreeListInfo.PoppedBlocks -
Region->FreeListInfo.PushedBlocks) *
BlockSize;
if (UNLIKELY(BytesInFreeList == 0))
return false;
const uptr AllocatedUserEnd = const uptr AllocatedUserEnd =
Region->MemMapInfo.AllocatedUser + Region->RegionBeg; Region->MemMapInfo.AllocatedUser + Region->RegionBeg;
const uptr CompactPtrBase = getCompactPtrBaseByClassId(ClassId); SinglyLinkedList<BatchGroupT> GroupsToRelease;
// ====================================================================== // {
// 1. Check if we have enough free blocks and if it's worth doing a page ScopedLock L(Region->FLLock);
// release.
// ====================================================================== //
if (ReleaseType != ReleaseToOS::ForceAll &&
!hasChanceToReleasePages(Region, BlockSize, BytesInFreeList,
ReleaseType)) {
return 0;
}
// ====================================================================== // BytesInFreeList = Region->MemMapInfo.AllocatedUser -
// 2. Determine which groups can release the pages. Use a heuristic to (Region->FreeListInfo.PoppedBlocks -
// gather groups that are candidates for doing a release. Region->FreeListInfo.PushedBlocks) *
// ====================================================================== // BlockSize;
SinglyLinkedList<BatchGroup> GroupsToRelease; if (UNLIKELY(BytesInFreeList == 0))
if (ReleaseType == ReleaseToOS::ForceAll) { return false;
GroupsToRelease = Region->FreeListInfo.BlockList;
Region->FreeListInfo.BlockList.clear();
} else {
GroupsToRelease = collectGroupsToRelease(
Region, BlockSize, AllocatedUserEnd, CompactPtrBase);
}
if (GroupsToRelease.empty())
return 0;
// Ideally, we should use a class like `ScopedUnlock`. However, this form of // ==================================================================== //
// unlocking is not supported by the thread-safety analysis. See // 1. Check if we have enough free blocks and if it's worth doing a page
// https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#no-alias-analysis // release.
// for more details. // ==================================================================== //
// Put it as local class so that we can mark the ctor/dtor with proper if (ReleaseType != ReleaseToOS::ForceAll &&
// annotations associated to the target lock. Note that accessing the !hasChanceToReleasePages(Region, BlockSize, BytesInFreeList,
// function variable in local class only works in thread-safety annotations. ReleaseType)) {
// TODO: Implement general `ScopedUnlock` when it's supported. return 0;
class FLLockScopedUnlock {
public:
FLLockScopedUnlock(RegionInfo *Region) RELEASE(Region->FLLock)
: R(Region) {
R->FLLock.assertHeld();
R->FLLock.unlock();
} }
~FLLockScopedUnlock() ACQUIRE(Region->FLLock) { R->FLLock.lock(); }
private: // ==================================================================== //
RegionInfo *R; // 2. Determine which groups can release the pages. Use a heuristic to
}; // gather groups that are candidates for doing a release.
// ==================================================================== //
if (ReleaseType == ReleaseToOS::ForceAll) {
GroupsToRelease = Region->FreeListInfo.BlockList;
Region->FreeListInfo.BlockList.clear();
} else {
GroupsToRelease =
collectGroupsToRelease(Region, BlockSize, AllocatedUserEnd,
getCompactPtrBaseByClassId(ClassId));
}
if (GroupsToRelease.empty())
return 0;
}
// Note that we have extracted the `GroupsToRelease` from region freelist. // Note that we have extracted the `GroupsToRelease` from region freelist.
// It's safe to let pushBlocks()/popBatches() access the remaining region // It's safe to let pushBlocks()/popBatches() access the remaining region
// freelist. In the steps 3 and 4, we will temporarily release the FLLock // freelist. In the steps 3 and 4, we will temporarily release the FLLock
// and lock it again before step 5. // and lock it again before step 5.
uptr ReleasedBytes = 0; // ==================================================================== //
{ // 3. Mark the free blocks in `GroupsToRelease` in the `PageReleaseContext`.
FLLockScopedUnlock UL(Region); // Then we can tell which pages are in-use by querying
// ==================================================================== // // `PageReleaseContext`.
// 3. Mark the free blocks in `GroupsToRelease` in the // ==================================================================== //
// `PageReleaseContext`. Then we can tell which pages are in-use by PageReleaseContext Context =
// querying `PageReleaseContext`. markFreeBlocks(Region, BlockSize, AllocatedUserEnd,
// ==================================================================== // getCompactPtrBaseByClassId(ClassId), GroupsToRelease);
PageReleaseContext Context = markFreeBlocks( if (UNLIKELY(!Context.hasBlockMarked())) {
Region, BlockSize, AllocatedUserEnd, CompactPtrBase, GroupsToRelease); mergeGroupsToReleaseBack(Region, GroupsToRelease);
if (UNLIKELY(!Context.hasBlockMarked())) { return 0;
ScopedLock L(Region->FLLock);
mergeGroupsToReleaseBack(Region, GroupsToRelease);
return 0;
}
// ==================================================================== //
// 4. Release the unused physical pages back to the OS.
// ==================================================================== //
RegionReleaseRecorder<MemMapT> Recorder(&Region->MemMapInfo.MemMap,
Region->RegionBeg,
Context.getReleaseOffset());
auto SkipRegion = [](UNUSED uptr RegionIndex) { return false; };
releaseFreeMemoryToOS(Context, Recorder, SkipRegion);
if (Recorder.getReleasedRangesCount() > 0) {
Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint = BytesInFreeList;
Region->ReleaseInfo.RangesReleased += Recorder.getReleasedRangesCount();
Region->ReleaseInfo.LastReleasedBytes = Recorder.getReleasedBytes();
}
Region->ReleaseInfo.LastReleaseAtNs = getMonotonicTimeFast();
ReleasedBytes = Recorder.getReleasedBytes();
} }
// ==================================================================== //
// 4. Release the unused physical pages back to the OS.
// ==================================================================== //
RegionReleaseRecorder<MemMapT> Recorder(&Region->MemMapInfo.MemMap,
Region->RegionBeg,
Context.getReleaseOffset());
auto SkipRegion = [](UNUSED uptr RegionIndex) { return false; };
releaseFreeMemoryToOS(Context, Recorder, SkipRegion);
if (Recorder.getReleasedRangesCount() > 0) {
Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint = BytesInFreeList;
Region->ReleaseInfo.RangesReleased += Recorder.getReleasedRangesCount();
Region->ReleaseInfo.LastReleasedBytes = Recorder.getReleasedBytes();
}
Region->ReleaseInfo.LastReleaseAtNs = getMonotonicTimeFast();
// ====================================================================== // // ====================================================================== //
// 5. Merge the `GroupsToRelease` back to the freelist. // 5. Merge the `GroupsToRelease` back to the freelist.
// ====================================================================== // // ====================================================================== //
mergeGroupsToReleaseBack(Region, GroupsToRelease); mergeGroupsToReleaseBack(Region, GroupsToRelease);
return ReleasedBytes; return Recorder.getReleasedBytes();
} }
bool hasChanceToReleasePages(RegionInfo *Region, uptr BlockSize, bool hasChanceToReleasePages(RegionInfo *Region, uptr BlockSize,
@ -1154,13 +1290,13 @@ private:
return true; return true;
} }
SinglyLinkedList<BatchGroup> SinglyLinkedList<BatchGroupT>
collectGroupsToRelease(RegionInfo *Region, const uptr BlockSize, collectGroupsToRelease(RegionInfo *Region, const uptr BlockSize,
const uptr AllocatedUserEnd, const uptr CompactPtrBase) const uptr AllocatedUserEnd, const uptr CompactPtrBase)
REQUIRES(Region->MMLock, Region->FLLock) { REQUIRES(Region->MMLock, Region->FLLock) {
const uptr GroupSize = (1U << GroupSizeLog); const uptr GroupSize = (1UL << GroupSizeLog);
const uptr PageSize = getPageSizeCached(); const uptr PageSize = getPageSizeCached();
SinglyLinkedList<BatchGroup> GroupsToRelease; SinglyLinkedList<BatchGroupT> GroupsToRelease;
// We are examining each group and will take the minimum distance to the // We are examining each group and will take the minimum distance to the
// release threshold as the next Region::TryReleaseThreshold(). Note that if // release threshold as the next Region::TryReleaseThreshold(). Note that if
@ -1169,8 +1305,8 @@ private:
// the comment on `SmallerBlockReleasePageDelta` for more details. // the comment on `SmallerBlockReleasePageDelta` for more details.
uptr MinDistToThreshold = GroupSize; uptr MinDistToThreshold = GroupSize;
for (BatchGroup *BG = Region->FreeListInfo.BlockList.front(), for (BatchGroupT *BG = Region->FreeListInfo.BlockList.front(),
*Prev = nullptr; *Prev = nullptr;
BG != nullptr;) { BG != nullptr;) {
// Group boundary is always GroupSize-aligned from CompactPtr base. The // Group boundary is always GroupSize-aligned from CompactPtr base. The
// layout of memory groups is like, // layout of memory groups is like,
@ -1254,7 +1390,7 @@ private:
} }
} }
// If `BG` is the first BatchGroup in the list, we only need to advance // If `BG` is the first BatchGroupT in the list, we only need to advance
// `BG` and call FreeListInfo.BlockList::pop_front(). No update is needed // `BG` and call FreeListInfo.BlockList::pop_front(). No update is needed
// for `Prev`. // for `Prev`.
// //
@ -1290,7 +1426,7 @@ private:
// Note that we need to advance before pushing this BatchGroup to // Note that we need to advance before pushing this BatchGroup to
// GroupsToRelease because it's a destructive operation. // GroupsToRelease because it's a destructive operation.
BatchGroup *Cur = BG; BatchGroupT *Cur = BG;
BG = BG->Next; BG = BG->Next;
// Ideally, we may want to update this only after successful release. // Ideally, we may want to update this only after successful release.
@ -1323,9 +1459,9 @@ private:
PageReleaseContext PageReleaseContext
markFreeBlocks(RegionInfo *Region, const uptr BlockSize, markFreeBlocks(RegionInfo *Region, const uptr BlockSize,
const uptr AllocatedUserEnd, const uptr CompactPtrBase, const uptr AllocatedUserEnd, const uptr CompactPtrBase,
SinglyLinkedList<BatchGroup> &GroupsToRelease) SinglyLinkedList<BatchGroupT> &GroupsToRelease)
REQUIRES(Region->MMLock) EXCLUDES(Region->FLLock) { REQUIRES(Region->MMLock) EXCLUDES(Region->FLLock) {
const uptr GroupSize = (1U << GroupSizeLog); const uptr GroupSize = (1UL << GroupSizeLog);
auto DecompactPtr = [CompactPtrBase](CompactPtrT CompactPtr) { auto DecompactPtr = [CompactPtrBase](CompactPtrT CompactPtr) {
return decompactPtrInternal(CompactPtrBase, CompactPtr); return decompactPtrInternal(CompactPtrBase, CompactPtr);
}; };
@ -1352,7 +1488,7 @@ private:
if (UNLIKELY(!Context.ensurePageMapAllocated())) if (UNLIKELY(!Context.ensurePageMapAllocated()))
return Context; return Context;
for (BatchGroup &BG : GroupsToRelease) { for (BatchGroupT &BG : GroupsToRelease) {
const uptr BatchGroupBase = const uptr BatchGroupBase =
decompactGroupBase(CompactPtrBase, BG.CompactPtrGroupBase); decompactGroupBase(CompactPtrBase, BG.CompactPtrGroupBase);
const uptr BatchGroupEnd = BatchGroupBase + GroupSize; const uptr BatchGroupEnd = BatchGroupBase + GroupSize;
@ -1400,8 +1536,10 @@ private:
} }
void mergeGroupsToReleaseBack(RegionInfo *Region, void mergeGroupsToReleaseBack(RegionInfo *Region,
SinglyLinkedList<BatchGroup> &GroupsToRelease) SinglyLinkedList<BatchGroupT> &GroupsToRelease)
REQUIRES(Region->MMLock, Region->FLLock) { REQUIRES(Region->MMLock) EXCLUDES(Region->FLLock) {
ScopedLock L(Region->FLLock);
// After merging two freelists, we may have redundant `BatchGroup`s that // After merging two freelists, we may have redundant `BatchGroup`s that
// need to be recycled. The number of unused `BatchGroup`s is expected to be // need to be recycled. The number of unused `BatchGroup`s is expected to be
// small. Pick a constant which is inferred from real programs. // small. Pick a constant which is inferred from real programs.
@ -1419,8 +1557,8 @@ private:
// Merge GroupsToRelease back to the Region::FreeListInfo.BlockList. Note // Merge GroupsToRelease back to the Region::FreeListInfo.BlockList. Note
// that both `Region->FreeListInfo.BlockList` and `GroupsToRelease` are // that both `Region->FreeListInfo.BlockList` and `GroupsToRelease` are
// sorted. // sorted.
for (BatchGroup *BG = Region->FreeListInfo.BlockList.front(), for (BatchGroupT *BG = Region->FreeListInfo.BlockList.front(),
*Prev = nullptr; *Prev = nullptr;
;) { ;) {
if (BG == nullptr || GroupsToRelease.empty()) { if (BG == nullptr || GroupsToRelease.empty()) {
if (!GroupsToRelease.empty()) if (!GroupsToRelease.empty())
@ -1437,8 +1575,8 @@ private:
continue; continue;
} }
BatchGroup *Cur = GroupsToRelease.front(); BatchGroupT *Cur = GroupsToRelease.front();
TransferBatch *UnusedTransferBatch = nullptr; TransferBatchT *UnusedTransferBatch = nullptr;
GroupsToRelease.pop_front(); GroupsToRelease.pop_front();
if (BG->CompactPtrGroupBase == Cur->CompactPtrGroupBase) { if (BG->CompactPtrGroupBase == Cur->CompactPtrGroupBase) {
@ -1454,7 +1592,7 @@ private:
if (Cur->Batches.front()->getCount() == MaxCachedPerBatch) { if (Cur->Batches.front()->getCount() == MaxCachedPerBatch) {
BG->Batches.append_back(&Cur->Batches); BG->Batches.append_back(&Cur->Batches);
} else { } else {
TransferBatch *NonFullBatch = Cur->Batches.front(); TransferBatchT *NonFullBatch = Cur->Batches.front();
Cur->Batches.pop_front(); Cur->Batches.pop_front();
const u16 NonFullBatchCount = NonFullBatch->getCount(); const u16 NonFullBatchCount = NonFullBatch->getCount();
// The remaining Batches in `Cur` are full. // The remaining Batches in `Cur` are full.
@ -1481,6 +1619,8 @@ private:
if (UNLIKELY(Idx + NeededSlots > MaxUnusedSize)) { if (UNLIKELY(Idx + NeededSlots > MaxUnusedSize)) {
ScopedLock L(BatchClassRegion->FLLock); ScopedLock L(BatchClassRegion->FLLock);
pushBatchClassBlocks(BatchClassRegion, Blocks, Idx); pushBatchClassBlocks(BatchClassRegion, Blocks, Idx);
if (conditionVariableEnabled())
BatchClassRegion->FLLockCV.notifyAll(BatchClassRegion->FLLock);
Idx = 0; Idx = 0;
} }
Blocks[Idx++] = Blocks[Idx++] =
@ -1516,15 +1656,20 @@ private:
if (Idx != 0) { if (Idx != 0) {
ScopedLock L(BatchClassRegion->FLLock); ScopedLock L(BatchClassRegion->FLLock);
pushBatchClassBlocks(BatchClassRegion, Blocks, Idx); pushBatchClassBlocks(BatchClassRegion, Blocks, Idx);
if (conditionVariableEnabled())
BatchClassRegion->FLLockCV.notifyAll(BatchClassRegion->FLLock);
} }
if (SCUDO_DEBUG) { if (SCUDO_DEBUG) {
BatchGroup *Prev = Region->FreeListInfo.BlockList.front(); BatchGroupT *Prev = Region->FreeListInfo.BlockList.front();
for (BatchGroup *Cur = Prev->Next; Cur != nullptr; for (BatchGroupT *Cur = Prev->Next; Cur != nullptr;
Prev = Cur, Cur = Cur->Next) { Prev = Cur, Cur = Cur->Next) {
CHECK_LT(Prev->CompactPtrGroupBase, Cur->CompactPtrGroupBase); CHECK_LT(Prev->CompactPtrGroupBase, Cur->CompactPtrGroupBase);
} }
} }
if (conditionVariableEnabled())
Region->FLLockCV.notifyAll(Region->FLLock);
} }
// TODO: `PrimaryBase` can be obtained from ReservedMemory. This needs to be // TODO: `PrimaryBase` can be obtained from ReservedMemory. This needs to be

View file

@ -10,7 +10,8 @@
namespace scudo { namespace scudo {
BufferPool<RegionPageMap::StaticBufferCount, RegionPageMap::StaticBufferSize> BufferPool<RegionPageMap::StaticBufferCount,
RegionPageMap::StaticBufferNumElements>
RegionPageMap::Buffers; RegionPageMap::Buffers;
} // namespace scudo } // namespace scudo

View file

@ -80,20 +80,53 @@ private:
MapPlatformData *Data = nullptr; MapPlatformData *Data = nullptr;
}; };
// A buffer pool which holds a fixed number of static buffers for fast buffer class FragmentationRecorder {
// allocation. If the request size is greater than `StaticBufferSize`, it'll public:
FragmentationRecorder() = default;
uptr getReleasedPagesCount() const { return ReleasedPagesCount; }
void releasePageRangeToOS(uptr From, uptr To) {
DCHECK_EQ((To - From) % getPageSizeCached(), 0U);
ReleasedPagesCount += (To - From) / getPageSizeCached();
}
private:
uptr ReleasedPagesCount = 0;
};
// A buffer pool which holds a fixed number of static buffers of `uptr` elements
// for fast buffer allocation. If the request size is greater than
// `StaticBufferNumElements` or if all the static buffers are in use, it'll
// delegate the allocation to map(). // delegate the allocation to map().
template <uptr StaticBufferCount, uptr StaticBufferSize> class BufferPool { template <uptr StaticBufferCount, uptr StaticBufferNumElements>
class BufferPool {
public: public:
// Preserve 1 bit in the `Mask` so that we don't need to do zero-check while // Preserve 1 bit in the `Mask` so that we don't need to do zero-check while
// extracting the least significant bit from the `Mask`. // extracting the least significant bit from the `Mask`.
static_assert(StaticBufferCount < SCUDO_WORDSIZE, ""); static_assert(StaticBufferCount < SCUDO_WORDSIZE, "");
static_assert(isAligned(StaticBufferSize, SCUDO_CACHE_LINE_SIZE), ""); static_assert(isAligned(StaticBufferNumElements * sizeof(uptr),
SCUDO_CACHE_LINE_SIZE),
"");
// Return a buffer which is at least `BufferSize`. struct Buffer {
uptr *getBuffer(const uptr BufferSize) { // Pointer to the buffer's memory, or nullptr if no buffer was allocated.
if (UNLIKELY(BufferSize > StaticBufferSize)) uptr *Data = nullptr;
return getDynamicBuffer(BufferSize);
// The index of the underlying static buffer, or StaticBufferCount if this
// buffer was dynamically allocated. This value is initially set to a poison
// value to aid debugging.
uptr BufferIndex = ~static_cast<uptr>(0);
// Only valid if BufferIndex == StaticBufferCount.
MemMapT MemMap = {};
};
// Return a zero-initialized buffer which can contain at least the given
// number of elements, or nullptr on failure.
Buffer getBuffer(const uptr NumElements) {
if (UNLIKELY(NumElements > StaticBufferNumElements))
return getDynamicBuffer(NumElements);
uptr index; uptr index;
{ {
@ -108,69 +141,55 @@ public:
} }
if (index >= StaticBufferCount) if (index >= StaticBufferCount)
return getDynamicBuffer(BufferSize); return getDynamicBuffer(NumElements);
const uptr Offset = index * StaticBufferSize; Buffer Buf;
memset(&RawBuffer[Offset], 0, StaticBufferSize); Buf.Data = &RawBuffer[index * StaticBufferNumElements];
return &RawBuffer[Offset]; Buf.BufferIndex = index;
memset(Buf.Data, 0, StaticBufferNumElements * sizeof(uptr));
return Buf;
} }
void releaseBuffer(uptr *Buffer, const uptr BufferSize) { void releaseBuffer(Buffer Buf) {
const uptr index = getStaticBufferIndex(Buffer, BufferSize); DCHECK_NE(Buf.Data, nullptr);
if (index < StaticBufferCount) { DCHECK_LE(Buf.BufferIndex, StaticBufferCount);
if (Buf.BufferIndex != StaticBufferCount) {
ScopedLock L(Mutex); ScopedLock L(Mutex);
DCHECK_EQ((Mask & (static_cast<uptr>(1) << index)), 0U); DCHECK_EQ((Mask & (static_cast<uptr>(1) << Buf.BufferIndex)), 0U);
Mask |= static_cast<uptr>(1) << index; Mask |= static_cast<uptr>(1) << Buf.BufferIndex;
} else { } else {
unmap(reinterpret_cast<void *>(Buffer), Buf.MemMap.unmap(Buf.MemMap.getBase(), Buf.MemMap.getCapacity());
roundUp(BufferSize, getPageSizeCached()));
} }
} }
bool isStaticBufferTestOnly(uptr *Buffer, uptr BufferSize) { bool isStaticBufferTestOnly(const Buffer &Buf) {
return getStaticBufferIndex(Buffer, BufferSize) < StaticBufferCount; DCHECK_NE(Buf.Data, nullptr);
DCHECK_LE(Buf.BufferIndex, StaticBufferCount);
return Buf.BufferIndex != StaticBufferCount;
} }
private: private:
uptr getStaticBufferIndex(uptr *Buffer, uptr BufferSize) { Buffer getDynamicBuffer(const uptr NumElements) {
if (UNLIKELY(BufferSize > StaticBufferSize))
return StaticBufferCount;
const uptr BufferBase = reinterpret_cast<uptr>(Buffer);
const uptr RawBufferBase = reinterpret_cast<uptr>(RawBuffer);
if (BufferBase < RawBufferBase ||
BufferBase >= RawBufferBase + sizeof(RawBuffer)) {
return StaticBufferCount;
}
DCHECK_LE(BufferSize, StaticBufferSize);
DCHECK_LE(BufferBase + BufferSize, RawBufferBase + sizeof(RawBuffer));
DCHECK_EQ((BufferBase - RawBufferBase) % StaticBufferSize, 0U);
const uptr index =
(BufferBase - RawBufferBase) / (StaticBufferSize * sizeof(uptr));
DCHECK_LT(index, StaticBufferCount);
return index;
}
uptr *getDynamicBuffer(const uptr BufferSize) {
// When using a heap-based buffer, precommit the pages backing the // When using a heap-based buffer, precommit the pages backing the
// Vmar by passing |MAP_PRECOMMIT| flag. This allows an optimization // Vmar by passing |MAP_PRECOMMIT| flag. This allows an optimization
// where page fault exceptions are skipped as the allocated memory // where page fault exceptions are skipped as the allocated memory
// is accessed. So far, this is only enabled on Fuchsia. It hasn't proven a // is accessed. So far, this is only enabled on Fuchsia. It hasn't proven a
// performance benefit on other platforms. // performance benefit on other platforms.
const uptr MmapFlags = MAP_ALLOWNOMEM | (SCUDO_FUCHSIA ? MAP_PRECOMMIT : 0); const uptr MmapFlags = MAP_ALLOWNOMEM | (SCUDO_FUCHSIA ? MAP_PRECOMMIT : 0);
return reinterpret_cast<uptr *>( const uptr MappedSize =
map(nullptr, roundUp(BufferSize, getPageSizeCached()), "scudo:counters", roundUp(NumElements * sizeof(uptr), getPageSizeCached());
MmapFlags, &MapData)); Buffer Buf;
if (Buf.MemMap.map(/*Addr=*/0, MappedSize, "scudo:counters", MmapFlags)) {
Buf.Data = reinterpret_cast<uptr *>(Buf.MemMap.getBase());
Buf.BufferIndex = StaticBufferCount;
}
return Buf;
} }
HybridMutex Mutex; HybridMutex Mutex;
// '1' means that buffer index is not used. '0' means the buffer is in use. // '1' means that buffer index is not used. '0' means the buffer is in use.
uptr Mask GUARDED_BY(Mutex) = ~static_cast<uptr>(0); uptr Mask GUARDED_BY(Mutex) = ~static_cast<uptr>(0);
uptr RawBuffer[StaticBufferCount * StaticBufferSize] GUARDED_BY(Mutex); uptr RawBuffer[StaticBufferCount * StaticBufferNumElements] GUARDED_BY(Mutex);
[[no_unique_address]] MapPlatformData MapData = {};
}; };
// A Region page map is used to record the usage of pages in the regions. It // A Region page map is used to record the usage of pages in the regions. It
@ -185,23 +204,17 @@ private:
class RegionPageMap { class RegionPageMap {
public: public:
RegionPageMap() RegionPageMap()
: Regions(0), : Regions(0), NumCounters(0), CounterSizeBitsLog(0), CounterMask(0),
NumCounters(0), PackingRatioLog(0), BitOffsetMask(0), SizePerRegion(0),
CounterSizeBitsLog(0), BufferNumElements(0) {}
CounterMask(0),
PackingRatioLog(0),
BitOffsetMask(0),
SizePerRegion(0),
BufferSize(0),
Buffer(nullptr) {}
RegionPageMap(uptr NumberOfRegions, uptr CountersPerRegion, uptr MaxValue) { RegionPageMap(uptr NumberOfRegions, uptr CountersPerRegion, uptr MaxValue) {
reset(NumberOfRegions, CountersPerRegion, MaxValue); reset(NumberOfRegions, CountersPerRegion, MaxValue);
} }
~RegionPageMap() { ~RegionPageMap() {
if (!isAllocated()) if (!isAllocated())
return; return;
Buffers.releaseBuffer(Buffer, BufferSize); Buffers.releaseBuffer(Buffer);
Buffer = nullptr; Buffer = {};
} }
// Lock of `StaticBuffer` is acquired conditionally and there's no easy way to // Lock of `StaticBuffer` is acquired conditionally and there's no easy way to
@ -216,7 +229,7 @@ public:
Regions = NumberOfRegion; Regions = NumberOfRegion;
NumCounters = CountersPerRegion; NumCounters = CountersPerRegion;
constexpr uptr MaxCounterBits = sizeof(*Buffer) * 8UL; constexpr uptr MaxCounterBits = sizeof(*Buffer.Data) * 8UL;
// Rounding counter storage size up to the power of two allows for using // Rounding counter storage size up to the power of two allows for using
// bit shifts calculating particular counter's Index and offset. // bit shifts calculating particular counter's Index and offset.
const uptr CounterSizeBits = const uptr CounterSizeBits =
@ -233,11 +246,11 @@ public:
SizePerRegion = SizePerRegion =
roundUp(NumCounters, static_cast<uptr>(1U) << PackingRatioLog) >> roundUp(NumCounters, static_cast<uptr>(1U) << PackingRatioLog) >>
PackingRatioLog; PackingRatioLog;
BufferSize = SizePerRegion * sizeof(*Buffer) * Regions; BufferNumElements = SizePerRegion * Regions;
Buffer = Buffers.getBuffer(BufferSize); Buffer = Buffers.getBuffer(BufferNumElements);
} }
bool isAllocated() const { return !!Buffer; } bool isAllocated() const { return Buffer.Data != nullptr; }
uptr getCount() const { return NumCounters; } uptr getCount() const { return NumCounters; }
@ -246,7 +259,8 @@ public:
DCHECK_LT(I, NumCounters); DCHECK_LT(I, NumCounters);
const uptr Index = I >> PackingRatioLog; const uptr Index = I >> PackingRatioLog;
const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog; const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
return (Buffer[Region * SizePerRegion + Index] >> BitOffset) & CounterMask; return (Buffer.Data[Region * SizePerRegion + Index] >> BitOffset) &
CounterMask;
} }
void inc(uptr Region, uptr I) const { void inc(uptr Region, uptr I) const {
@ -255,8 +269,8 @@ public:
const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog; const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
DCHECK_LT(BitOffset, SCUDO_WORDSIZE); DCHECK_LT(BitOffset, SCUDO_WORDSIZE);
DCHECK_EQ(isAllCounted(Region, I), false); DCHECK_EQ(isAllCounted(Region, I), false);
Buffer[Region * SizePerRegion + Index] += static_cast<uptr>(1U) Buffer.Data[Region * SizePerRegion + Index] += static_cast<uptr>(1U)
<< BitOffset; << BitOffset;
} }
void incN(uptr Region, uptr I, uptr N) const { void incN(uptr Region, uptr I, uptr N) const {
@ -267,7 +281,7 @@ public:
const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog; const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
DCHECK_LT(BitOffset, SCUDO_WORDSIZE); DCHECK_LT(BitOffset, SCUDO_WORDSIZE);
DCHECK_EQ(isAllCounted(Region, I), false); DCHECK_EQ(isAllCounted(Region, I), false);
Buffer[Region * SizePerRegion + Index] += N << BitOffset; Buffer.Data[Region * SizePerRegion + Index] += N << BitOffset;
} }
void incRange(uptr Region, uptr From, uptr To) const { void incRange(uptr Region, uptr From, uptr To) const {
@ -286,7 +300,7 @@ public:
const uptr Index = I >> PackingRatioLog; const uptr Index = I >> PackingRatioLog;
const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog; const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
DCHECK_LT(BitOffset, SCUDO_WORDSIZE); DCHECK_LT(BitOffset, SCUDO_WORDSIZE);
Buffer[Region * SizePerRegion + Index] |= CounterMask << BitOffset; Buffer.Data[Region * SizePerRegion + Index] |= CounterMask << BitOffset;
} }
void setAsAllCountedRange(uptr Region, uptr From, uptr To) const { void setAsAllCountedRange(uptr Region, uptr From, uptr To) const {
DCHECK_LE(From, To); DCHECK_LE(From, To);
@ -309,9 +323,16 @@ public:
return get(Region, I) == CounterMask; return get(Region, I) == CounterMask;
} }
uptr getBufferSize() const { return BufferSize; } uptr getBufferNumElements() const { return BufferNumElements; }
private: private:
// We may consider making this configurable if there are cases which may
// benefit from this.
static const uptr StaticBufferCount = 2U;
static const uptr StaticBufferNumElements = 512U;
using BufferPoolT = BufferPool<StaticBufferCount, StaticBufferNumElements>;
static BufferPoolT Buffers;
uptr Regions; uptr Regions;
uptr NumCounters; uptr NumCounters;
uptr CounterSizeBitsLog; uptr CounterSizeBitsLog;
@ -320,14 +341,8 @@ private:
uptr BitOffsetMask; uptr BitOffsetMask;
uptr SizePerRegion; uptr SizePerRegion;
uptr BufferSize; uptr BufferNumElements;
uptr *Buffer; BufferPoolT::Buffer Buffer;
// We may consider making this configurable if there are cases which may
// benefit from this.
static const uptr StaticBufferCount = 2U;
static const uptr StaticBufferSize = 512U;
static BufferPool<StaticBufferCount, StaticBufferSize> Buffers;
}; };
template <class ReleaseRecorderT> class FreePagesRangeTracker { template <class ReleaseRecorderT> class FreePagesRangeTracker {

View file

@ -24,11 +24,7 @@ public:
Message.vappend(Format, Args); Message.vappend(Format, Args);
va_end(Args); va_end(Args);
} }
NORETURN ~ScopedErrorReport() { NORETURN ~ScopedErrorReport() { reportRawError(Message.data()); }
outputRaw(Message.data());
setAbortMessage(Message.data());
die();
}
private: private:
ScopedString Message; ScopedString Message;
@ -36,18 +32,6 @@ private:
inline void NORETURN trap() { __builtin_trap(); } inline void NORETURN trap() { __builtin_trap(); }
void NORETURN reportSoftRSSLimit(uptr RssLimitMb) {
ScopedErrorReport Report;
Report.append("Soft RSS limit of %zu MB exhausted, current RSS is %zu MB\n",
RssLimitMb, GetRSS() >> 20);
}
void NORETURN reportHardRSSLimit(uptr RssLimitMb) {
ScopedErrorReport Report;
Report.append("Hard RSS limit of %zu MB exhausted, current RSS is %zu MB\n",
RssLimitMb, GetRSS() >> 20);
}
// This could potentially be called recursively if a CHECK fails in the reports. // This could potentially be called recursively if a CHECK fails in the reports.
void NORETURN reportCheckFailed(const char *File, int Line, void NORETURN reportCheckFailed(const char *File, int Line,
const char *Condition, u64 Value1, u64 Value2) { const char *Condition, u64 Value1, u64 Value2) {
@ -67,6 +51,13 @@ void NORETURN reportError(const char *Message) {
Report.append("%s\n", Message); Report.append("%s\n", Message);
} }
// Generic fatal error message without ScopedString.
void NORETURN reportRawError(const char *Message) {
outputRaw(Message);
setAbortMessage(Message);
die();
}
void NORETURN reportInvalidFlag(const char *FlagType, const char *Value) { void NORETURN reportInvalidFlag(const char *FlagType, const char *Value) {
ScopedErrorReport Report; ScopedErrorReport Report;
Report.append("invalid value for %s option: '%s'\n", FlagType, Value); Report.append("invalid value for %s option: '%s'\n", FlagType, Value);
@ -79,14 +70,6 @@ void NORETURN reportHeaderCorruption(void *Ptr) {
Report.append("corrupted chunk header at address %p\n", Ptr); Report.append("corrupted chunk header at address %p\n", Ptr);
} }
// Two threads have attempted to modify a chunk header at the same time. This is
// symptomatic of a race-condition in the application code, or general lack of
// proper locking.
void NORETURN reportHeaderRace(void *Ptr) {
ScopedErrorReport Report;
Report.append("race on chunk header at address %p\n", Ptr);
}
// The allocator was compiled with parameters that conflict with field size // The allocator was compiled with parameters that conflict with field size
// requirements. // requirements.
void NORETURN reportSanityCheckError(const char *Field) { void NORETURN reportSanityCheckError(const char *Field) {

View file

@ -15,15 +15,17 @@ namespace scudo {
// Reports are *fatal* unless stated otherwise. // Reports are *fatal* unless stated otherwise.
// Generic error. // Generic error, adds newline to end of message.
void NORETURN reportError(const char *Message); void NORETURN reportError(const char *Message);
// Generic error, but the message is not modified.
void NORETURN reportRawError(const char *Message);
// Flags related errors. // Flags related errors.
void NORETURN reportInvalidFlag(const char *FlagType, const char *Value); void NORETURN reportInvalidFlag(const char *FlagType, const char *Value);
// Chunk header related errors. // Chunk header related errors.
void NORETURN reportHeaderCorruption(void *Ptr); void NORETURN reportHeaderCorruption(void *Ptr);
void NORETURN reportHeaderRace(void *Ptr);
// Sanity checks related error. // Sanity checks related error.
void NORETURN reportSanityCheckError(const char *Field); void NORETURN reportSanityCheckError(const char *Field);
@ -34,8 +36,6 @@ void NORETURN reportAllocationSizeTooBig(uptr UserSize, uptr TotalSize,
uptr MaxSize); uptr MaxSize);
void NORETURN reportOutOfBatchClass(); void NORETURN reportOutOfBatchClass();
void NORETURN reportOutOfMemory(uptr RequestedSize); void NORETURN reportOutOfMemory(uptr RequestedSize);
void NORETURN reportSoftRSSLimit(uptr RssLimitMb);
void NORETURN reportHardRSSLimit(uptr RssLimitMb);
enum class AllocatorAction : u8 { enum class AllocatorAction : u8 {
Recycling, Recycling,
Deallocating, Deallocating,

View file

@ -0,0 +1,58 @@
//===-- report_linux.cpp ----------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "platform.h"
#if SCUDO_LINUX || SCUDO_TRUSTY
#include "common.h"
#include "internal_defs.h"
#include "report.h"
#include "report_linux.h"
#include "string_utils.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
namespace scudo {
// Fatal internal map() error (potentially OOM related).
void NORETURN reportMapError(uptr SizeIfOOM) {
char Error[128] = "Scudo ERROR: internal map failure\n";
if (SizeIfOOM) {
formatString(
Error, sizeof(Error),
"Scudo ERROR: internal map failure (NO MEMORY) requesting %zuKB\n",
SizeIfOOM >> 10);
}
reportRawError(Error);
}
void NORETURN reportUnmapError(uptr Addr, uptr Size) {
char Error[128];
formatString(Error, sizeof(Error),
"Scudo ERROR: internal unmap failure (error desc=%s) Addr 0x%zx "
"Size %zu\n",
strerror(errno), Addr, Size);
reportRawError(Error);
}
void NORETURN reportProtectError(uptr Addr, uptr Size, int Prot) {
char Error[128];
formatString(
Error, sizeof(Error),
"Scudo ERROR: internal protect failure (error desc=%s) Addr 0x%zx "
"Size %zu Prot %x\n",
strerror(errno), Addr, Size, Prot);
reportRawError(Error);
}
} // namespace scudo
#endif // SCUDO_LINUX || SCUDO_TRUSTY

View file

@ -0,0 +1,34 @@
//===-- report_linux.h ------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef SCUDO_REPORT_LINUX_H_
#define SCUDO_REPORT_LINUX_H_
#include "platform.h"
#if SCUDO_LINUX || SCUDO_TRUSTY
#include "internal_defs.h"
namespace scudo {
// Report a fatal error when a map call fails. SizeIfOOM shall
// hold the requested size on an out-of-memory error, 0 otherwise.
void NORETURN reportMapError(uptr SizeIfOOM = 0);
// Report a fatal error when an unmap call fails.
void NORETURN reportUnmapError(uptr Addr, uptr Size);
// Report a fatal error when a mprotect call fails.
void NORETURN reportProtectError(uptr Addr, uptr Size, int Prot);
} // namespace scudo
#endif // SCUDO_LINUX || SCUDO_TRUSTY
#endif // SCUDO_REPORT_LINUX_H_

View file

@ -1,37 +0,0 @@
//===-- common.cpp ----------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "rss_limit_checker.h"
#include "atomic_helpers.h"
#include "string_utils.h"
namespace scudo {
void RssLimitChecker::check(u64 NextCheck) {
// The interval for the checks is 250ms.
static constexpr u64 CheckInterval = 250 * 1000000;
// Early return in case another thread already did the calculation.
if (!atomic_compare_exchange_strong(&RssNextCheckAtNS, &NextCheck,
getMonotonicTime() + CheckInterval,
memory_order_relaxed)) {
return;
}
const uptr CurrentRssMb = GetRSS() >> 20;
RssLimitExceeded Result = RssLimitExceeded::Neither;
if (UNLIKELY(HardRssLimitMb && HardRssLimitMb < CurrentRssMb))
Result = RssLimitExceeded::Hard;
else if (UNLIKELY(SoftRssLimitMb && SoftRssLimitMb < CurrentRssMb))
Result = RssLimitExceeded::Soft;
atomic_store_relaxed(&RssLimitStatus, static_cast<u8>(Result));
}
} // namespace scudo

View file

@ -1,63 +0,0 @@
//===-- common.h ------------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef SCUDO_RSS_LIMIT_CHECKER_H_
#define SCUDO_RSS_LIMIT_CHECKER_H_
#include "atomic_helpers.h"
#include "common.h"
#include "internal_defs.h"
namespace scudo {
class RssLimitChecker {
public:
enum RssLimitExceeded {
Neither,
Soft,
Hard,
};
void init(int SoftRssLimitMb, int HardRssLimitMb) {
CHECK_GE(SoftRssLimitMb, 0);
CHECK_GE(HardRssLimitMb, 0);
this->SoftRssLimitMb = static_cast<uptr>(SoftRssLimitMb);
this->HardRssLimitMb = static_cast<uptr>(HardRssLimitMb);
}
// Opportunistic RSS limit check. This will update the RSS limit status, if
// it can, every 250ms, otherwise it will just return the current one.
RssLimitExceeded getRssLimitExceeded() {
if (!HardRssLimitMb && !SoftRssLimitMb)
return RssLimitExceeded::Neither;
u64 NextCheck = atomic_load_relaxed(&RssNextCheckAtNS);
u64 Now = getMonotonicTime();
if (UNLIKELY(Now >= NextCheck))
check(NextCheck);
return static_cast<RssLimitExceeded>(atomic_load_relaxed(&RssLimitStatus));
}
uptr getSoftRssLimit() const { return SoftRssLimitMb; }
uptr getHardRssLimit() const { return HardRssLimitMb; }
private:
void check(u64 NextCheck);
uptr SoftRssLimitMb = 0;
uptr HardRssLimitMb = 0;
atomic_u64 RssNextCheckAtNS = {};
atomic_u8 RssLimitStatus = {};
};
} // namespace scudo
#endif // SCUDO_RSS_LIMIT_CHECKER_H_

View file

@ -72,11 +72,26 @@ static inline void unmap(LargeBlock::Header *H) {
MemMap.unmap(MemMap.getBase(), MemMap.getCapacity()); MemMap.unmap(MemMap.getBase(), MemMap.getCapacity());
} }
namespace {
struct CachedBlock {
uptr CommitBase = 0;
uptr CommitSize = 0;
uptr BlockBegin = 0;
MemMapT MemMap = {};
u64 Time = 0;
bool isValid() { return CommitBase != 0; }
void invalidate() { CommitBase = 0; }
};
} // namespace
template <typename Config> class MapAllocatorNoCache { template <typename Config> class MapAllocatorNoCache {
public: public:
void init(UNUSED s32 ReleaseToOsInterval) {} void init(UNUSED s32 ReleaseToOsInterval) {}
bool retrieve(UNUSED Options Options, UNUSED uptr Size, UNUSED uptr Alignment, bool retrieve(UNUSED Options Options, UNUSED uptr Size, UNUSED uptr Alignment,
UNUSED LargeBlock::Header **H, UNUSED bool *Zeroed) { UNUSED uptr HeadersSize, UNUSED LargeBlock::Header **H,
UNUSED bool *Zeroed) {
return false; return false;
} }
void store(UNUSED Options Options, LargeBlock::Header *H) { unmap(H); } void store(UNUSED Options Options, LargeBlock::Header *H) { unmap(H); }
@ -102,20 +117,22 @@ public:
static const uptr MaxUnusedCachePages = 4U; static const uptr MaxUnusedCachePages = 4U;
template <typename Config> template <typename Config>
void mapSecondary(Options Options, uptr CommitBase, uptr CommitSize, bool mapSecondary(const Options &Options, uptr CommitBase, uptr CommitSize,
uptr AllocPos, uptr Flags, MemMapT &MemMap) { uptr AllocPos, uptr Flags, MemMapT &MemMap) {
Flags |= MAP_RESIZABLE;
Flags |= MAP_ALLOWNOMEM;
const uptr MaxUnusedCacheBytes = MaxUnusedCachePages * getPageSizeCached(); const uptr MaxUnusedCacheBytes = MaxUnusedCachePages * getPageSizeCached();
if (useMemoryTagging<Config>(Options) && CommitSize > MaxUnusedCacheBytes) { if (useMemoryTagging<Config>(Options) && CommitSize > MaxUnusedCacheBytes) {
const uptr UntaggedPos = Max(AllocPos, CommitBase + MaxUnusedCacheBytes); const uptr UntaggedPos = Max(AllocPos, CommitBase + MaxUnusedCacheBytes);
MemMap.remap(CommitBase, UntaggedPos - CommitBase, "scudo:secondary", return MemMap.remap(CommitBase, UntaggedPos - CommitBase, "scudo:secondary",
MAP_RESIZABLE | MAP_MEMTAG | Flags); MAP_MEMTAG | Flags) &&
MemMap.remap(UntaggedPos, CommitBase + CommitSize - UntaggedPos, MemMap.remap(UntaggedPos, CommitBase + CommitSize - UntaggedPos,
"scudo:secondary", MAP_RESIZABLE | Flags); "scudo:secondary", Flags);
} else { } else {
const uptr RemapFlags = const uptr RemapFlags =
MAP_RESIZABLE | (useMemoryTagging<Config>(Options) ? MAP_MEMTAG : 0) | (useMemoryTagging<Config>(Options) ? MAP_MEMTAG : 0) | Flags;
Flags; return MemMap.remap(CommitBase, CommitSize, "scudo:secondary", RemapFlags);
MemMap.remap(CommitBase, CommitSize, "scudo:secondary", RemapFlags);
} }
} }
@ -138,17 +155,24 @@ public:
void getStats(ScopedString *Str) { void getStats(ScopedString *Str) {
ScopedLock L(Mutex); ScopedLock L(Mutex);
uptr Integral;
uptr Fractional;
computePercentage(SuccessfulRetrieves, CallsToRetrieve, &Integral,
&Fractional);
Str->append("Stats: MapAllocatorCache: EntriesCount: %d, " Str->append("Stats: MapAllocatorCache: EntriesCount: %d, "
"MaxEntriesCount: %u, MaxEntrySize: %zu\n", "MaxEntriesCount: %u, MaxEntrySize: %zu\n",
EntriesCount, atomic_load_relaxed(&MaxEntriesCount), EntriesCount, atomic_load_relaxed(&MaxEntriesCount),
atomic_load_relaxed(&MaxEntrySize)); atomic_load_relaxed(&MaxEntrySize));
Str->append("Stats: CacheRetrievalStats: SuccessRate: %u/%u "
"(%zu.%02zu%%)\n",
SuccessfulRetrieves, CallsToRetrieve, Integral, Fractional);
for (CachedBlock Entry : Entries) { for (CachedBlock Entry : Entries) {
if (!Entry.CommitBase) if (!Entry.isValid())
continue; continue;
Str->append("StartBlockAddress: 0x%zx, EndBlockAddress: 0x%zx, " Str->append("StartBlockAddress: 0x%zx, EndBlockAddress: 0x%zx, "
"BlockSize: %zu\n", "BlockSize: %zu %s\n",
Entry.CommitBase, Entry.CommitBase + Entry.CommitSize, Entry.CommitBase, Entry.CommitBase + Entry.CommitSize,
Entry.CommitSize); Entry.CommitSize, Entry.Time == 0 ? "[R]" : "");
} }
} }
@ -166,7 +190,7 @@ public:
setOption(Option::ReleaseInterval, static_cast<sptr>(ReleaseToOsInterval)); setOption(Option::ReleaseInterval, static_cast<sptr>(ReleaseToOsInterval));
} }
void store(Options Options, LargeBlock::Header *H) EXCLUDES(Mutex) { void store(const Options &Options, LargeBlock::Header *H) EXCLUDES(Mutex) {
if (!canCache(H->CommitSize)) if (!canCache(H->CommitSize))
return unmap(H); return unmap(H);
@ -195,7 +219,7 @@ public:
MAP_NOACCESS); MAP_NOACCESS);
} }
} else if (Interval == 0) { } else if (Interval == 0) {
Entry.MemMap.releasePagesToOS(Entry.CommitBase, Entry.CommitSize); Entry.MemMap.releaseAndZeroPagesToOS(Entry.CommitBase, Entry.CommitSize);
Entry.Time = 0; Entry.Time = 0;
} }
do { do {
@ -210,7 +234,7 @@ public:
if (CacheConfig::QuarantineSize && useMemoryTagging<Config>(Options)) { if (CacheConfig::QuarantineSize && useMemoryTagging<Config>(Options)) {
QuarantinePos = QuarantinePos =
(QuarantinePos + 1) % Max(CacheConfig::QuarantineSize, 1u); (QuarantinePos + 1) % Max(CacheConfig::QuarantineSize, 1u);
if (!Quarantine[QuarantinePos].CommitBase) { if (!Quarantine[QuarantinePos].isValid()) {
Quarantine[QuarantinePos] = Entry; Quarantine[QuarantinePos] = Entry;
return; return;
} }
@ -225,7 +249,7 @@ public:
EmptyCache = true; EmptyCache = true;
} else { } else {
for (u32 I = 0; I < MaxCount; I++) { for (u32 I = 0; I < MaxCount; I++) {
if (Entries[I].CommitBase) if (Entries[I].isValid())
continue; continue;
if (I != 0) if (I != 0)
Entries[I] = Entries[0]; Entries[I] = Entries[0];
@ -246,26 +270,31 @@ public:
Entry.MemMap.unmap(Entry.MemMap.getBase(), Entry.MemMap.getCapacity()); Entry.MemMap.unmap(Entry.MemMap.getBase(), Entry.MemMap.getCapacity());
} }
bool retrieve(Options Options, uptr Size, uptr Alignment, bool retrieve(Options Options, uptr Size, uptr Alignment, uptr HeadersSize,
LargeBlock::Header **H, bool *Zeroed) EXCLUDES(Mutex) { LargeBlock::Header **H, bool *Zeroed) EXCLUDES(Mutex) {
const uptr PageSize = getPageSizeCached(); const uptr PageSize = getPageSizeCached();
const u32 MaxCount = atomic_load_relaxed(&MaxEntriesCount); const u32 MaxCount = atomic_load_relaxed(&MaxEntriesCount);
// 10% of the requested size proved to be the optimal choice for
// retrieving cached blocks after testing several options.
constexpr u32 FragmentedBytesDivisor = 10;
bool Found = false; bool Found = false;
CachedBlock Entry; CachedBlock Entry;
uptr HeaderPos = 0; uptr EntryHeaderPos = 0;
{ {
ScopedLock L(Mutex); ScopedLock L(Mutex);
CallsToRetrieve++;
if (EntriesCount == 0) if (EntriesCount == 0)
return false; return false;
u32 OptimalFitIndex = 0;
uptr MinDiff = UINTPTR_MAX;
for (u32 I = 0; I < MaxCount; I++) { for (u32 I = 0; I < MaxCount; I++) {
const uptr CommitBase = Entries[I].CommitBase; if (!Entries[I].isValid())
if (!CommitBase)
continue; continue;
const uptr CommitBase = Entries[I].CommitBase;
const uptr CommitSize = Entries[I].CommitSize; const uptr CommitSize = Entries[I].CommitSize;
const uptr AllocPos = const uptr AllocPos =
roundDown(CommitBase + CommitSize - Size, Alignment); roundDown(CommitBase + CommitSize - Size, Alignment);
HeaderPos = const uptr HeaderPos = AllocPos - HeadersSize;
AllocPos - Chunk::getHeaderSize() - LargeBlock::getHeaderSize();
if (HeaderPos > CommitBase + CommitSize) if (HeaderPos > CommitBase + CommitSize)
continue; continue;
if (HeaderPos < CommitBase || if (HeaderPos < CommitBase ||
@ -273,17 +302,36 @@ public:
continue; continue;
} }
Found = true; Found = true;
Entry = Entries[I]; const uptr Diff = HeaderPos - CommitBase;
Entries[I].CommitBase = 0; // immediately use a cached block if it's size is close enough to the
// requested size.
const uptr MaxAllowedFragmentedBytes =
(CommitBase + CommitSize - HeaderPos) / FragmentedBytesDivisor;
if (Diff <= MaxAllowedFragmentedBytes) {
OptimalFitIndex = I;
EntryHeaderPos = HeaderPos;
break;
}
// keep track of the smallest cached block
// that is greater than (AllocSize + HeaderSize)
if (Diff > MinDiff)
continue;
OptimalFitIndex = I;
MinDiff = Diff;
EntryHeaderPos = HeaderPos;
}
if (Found) {
Entry = Entries[OptimalFitIndex];
Entries[OptimalFitIndex].invalidate();
EntriesCount--; EntriesCount--;
break; SuccessfulRetrieves++;
} }
} }
if (!Found) if (!Found)
return false; return false;
*H = reinterpret_cast<LargeBlock::Header *>( *H = reinterpret_cast<LargeBlock::Header *>(
LargeBlock::addHeaderTag<Config>(HeaderPos)); LargeBlock::addHeaderTag<Config>(EntryHeaderPos));
*Zeroed = Entry.Time == 0; *Zeroed = Entry.Time == 0;
if (useMemoryTagging<Config>(Options)) if (useMemoryTagging<Config>(Options))
Entry.MemMap.setMemoryPermission(Entry.CommitBase, Entry.CommitSize, 0); Entry.MemMap.setMemoryPermission(Entry.CommitBase, Entry.CommitSize, 0);
@ -295,8 +343,7 @@ public:
} else if (Entry.BlockBegin < NewBlockBegin) { } else if (Entry.BlockBegin < NewBlockBegin) {
storeTags(Entry.BlockBegin, NewBlockBegin); storeTags(Entry.BlockBegin, NewBlockBegin);
} else { } else {
storeTags(untagPointer(NewBlockBegin), storeTags(untagPointer(NewBlockBegin), untagPointer(Entry.BlockBegin));
untagPointer(Entry.BlockBegin));
} }
} }
(*H)->CommitBase = Entry.CommitBase; (*H)->CommitBase = Entry.CommitBase;
@ -338,15 +385,15 @@ public:
void disableMemoryTagging() EXCLUDES(Mutex) { void disableMemoryTagging() EXCLUDES(Mutex) {
ScopedLock L(Mutex); ScopedLock L(Mutex);
for (u32 I = 0; I != CacheConfig::QuarantineSize; ++I) { for (u32 I = 0; I != CacheConfig::QuarantineSize; ++I) {
if (Quarantine[I].CommitBase) { if (Quarantine[I].isValid()) {
MemMapT &MemMap = Quarantine[I].MemMap; MemMapT &MemMap = Quarantine[I].MemMap;
MemMap.unmap(MemMap.getBase(), MemMap.getCapacity()); MemMap.unmap(MemMap.getBase(), MemMap.getCapacity());
Quarantine[I].CommitBase = 0; Quarantine[I].invalidate();
} }
} }
const u32 MaxCount = atomic_load_relaxed(&MaxEntriesCount); const u32 MaxCount = atomic_load_relaxed(&MaxEntriesCount);
for (u32 I = 0; I < MaxCount; I++) { for (u32 I = 0; I < MaxCount; I++) {
if (Entries[I].CommitBase) { if (Entries[I].isValid()) {
Entries[I].MemMap.setMemoryPermission(Entries[I].CommitBase, Entries[I].MemMap.setMemoryPermission(Entries[I].CommitBase,
Entries[I].CommitSize, 0); Entries[I].CommitSize, 0);
} }
@ -367,10 +414,10 @@ private:
{ {
ScopedLock L(Mutex); ScopedLock L(Mutex);
for (uptr I = 0; I < CacheConfig::EntriesArraySize; I++) { for (uptr I = 0; I < CacheConfig::EntriesArraySize; I++) {
if (!Entries[I].CommitBase) if (!Entries[I].isValid())
continue; continue;
MapInfo[N] = Entries[I].MemMap; MapInfo[N] = Entries[I].MemMap;
Entries[I].CommitBase = 0; Entries[I].invalidate();
N++; N++;
} }
EntriesCount = 0; EntriesCount = 0;
@ -382,23 +429,15 @@ private:
} }
} }
struct CachedBlock {
uptr CommitBase = 0;
uptr CommitSize = 0;
uptr BlockBegin = 0;
MemMapT MemMap = {};
u64 Time = 0;
};
void releaseIfOlderThan(CachedBlock &Entry, u64 Time) REQUIRES(Mutex) { void releaseIfOlderThan(CachedBlock &Entry, u64 Time) REQUIRES(Mutex) {
if (!Entry.CommitBase || !Entry.Time) if (!Entry.isValid() || !Entry.Time)
return; return;
if (Entry.Time > Time) { if (Entry.Time > Time) {
if (OldestTime == 0 || Entry.Time < OldestTime) if (OldestTime == 0 || Entry.Time < OldestTime)
OldestTime = Entry.Time; OldestTime = Entry.Time;
return; return;
} }
Entry.MemMap.releasePagesToOS(Entry.CommitBase, Entry.CommitSize); Entry.MemMap.releaseAndZeroPagesToOS(Entry.CommitBase, Entry.CommitSize);
Entry.Time = 0; Entry.Time = 0;
} }
@ -421,6 +460,8 @@ private:
u64 OldestTime GUARDED_BY(Mutex) = 0; u64 OldestTime GUARDED_BY(Mutex) = 0;
u32 IsFullEvents GUARDED_BY(Mutex) = 0; u32 IsFullEvents GUARDED_BY(Mutex) = 0;
atomic_s32 ReleaseToOsIntervalMs = {}; atomic_s32 ReleaseToOsIntervalMs = {};
u32 CallsToRetrieve GUARDED_BY(Mutex) = 0;
u32 SuccessfulRetrieves GUARDED_BY(Mutex) = 0;
CachedBlock Entries[CacheConfig::EntriesArraySize] GUARDED_BY(Mutex) = {}; CachedBlock Entries[CacheConfig::EntriesArraySize] GUARDED_BY(Mutex) = {};
NonZeroLengthArray<CachedBlock, CacheConfig::QuarantineSize> NonZeroLengthArray<CachedBlock, CacheConfig::QuarantineSize>
@ -439,11 +480,11 @@ public:
S->link(&Stats); S->link(&Stats);
} }
void *allocate(Options Options, uptr Size, uptr AlignmentHint = 0, void *allocate(const Options &Options, uptr Size, uptr AlignmentHint = 0,
uptr *BlockEnd = nullptr, uptr *BlockEnd = nullptr,
FillContentsMode FillContents = NoFill); FillContentsMode FillContents = NoFill);
void deallocate(Options Options, void *Ptr); void deallocate(const Options &Options, void *Ptr);
static uptr getBlockEnd(void *Ptr) { static uptr getBlockEnd(void *Ptr) {
auto *B = LargeBlock::getHeader<Config>(Ptr); auto *B = LargeBlock::getHeader<Config>(Ptr);
@ -454,6 +495,10 @@ public:
return getBlockEnd(Ptr) - reinterpret_cast<uptr>(Ptr); return getBlockEnd(Ptr) - reinterpret_cast<uptr>(Ptr);
} }
static constexpr uptr getHeadersSize() {
return Chunk::getHeaderSize() + LargeBlock::getHeaderSize();
}
void disable() NO_THREAD_SAFETY_ANALYSIS { void disable() NO_THREAD_SAFETY_ANALYSIS {
Mutex.lock(); Mutex.lock();
Cache.disable(); Cache.disable();
@ -494,6 +539,7 @@ private:
DoublyLinkedList<LargeBlock::Header> InUseBlocks GUARDED_BY(Mutex); DoublyLinkedList<LargeBlock::Header> InUseBlocks GUARDED_BY(Mutex);
uptr AllocatedBytes GUARDED_BY(Mutex) = 0; uptr AllocatedBytes GUARDED_BY(Mutex) = 0;
uptr FreedBytes GUARDED_BY(Mutex) = 0; uptr FreedBytes GUARDED_BY(Mutex) = 0;
uptr FragmentedBytes GUARDED_BY(Mutex) = 0;
uptr LargestSize GUARDED_BY(Mutex) = 0; uptr LargestSize GUARDED_BY(Mutex) = 0;
u32 NumberOfAllocs GUARDED_BY(Mutex) = 0; u32 NumberOfAllocs GUARDED_BY(Mutex) = 0;
u32 NumberOfFrees GUARDED_BY(Mutex) = 0; u32 NumberOfFrees GUARDED_BY(Mutex) = 0;
@ -512,24 +558,23 @@ private:
// the committed memory will amount to something close to Size - AlignmentHint // the committed memory will amount to something close to Size - AlignmentHint
// (pending rounding and headers). // (pending rounding and headers).
template <typename Config> template <typename Config>
void *MapAllocator<Config>::allocate(Options Options, uptr Size, uptr Alignment, void *MapAllocator<Config>::allocate(const Options &Options, uptr Size,
uptr *BlockEndPtr, uptr Alignment, uptr *BlockEndPtr,
FillContentsMode FillContents) { FillContentsMode FillContents) {
if (Options.get(OptionBit::AddLargeAllocationSlack)) if (Options.get(OptionBit::AddLargeAllocationSlack))
Size += 1UL << SCUDO_MIN_ALIGNMENT_LOG; Size += 1UL << SCUDO_MIN_ALIGNMENT_LOG;
Alignment = Max(Alignment, uptr(1U) << SCUDO_MIN_ALIGNMENT_LOG); Alignment = Max(Alignment, uptr(1U) << SCUDO_MIN_ALIGNMENT_LOG);
const uptr PageSize = getPageSizeCached(); const uptr PageSize = getPageSizeCached();
uptr RoundedSize =
roundUp(roundUp(Size, Alignment) + LargeBlock::getHeaderSize() +
Chunk::getHeaderSize(),
PageSize);
if (Alignment > PageSize)
RoundedSize += Alignment - PageSize;
if (Alignment < PageSize && Cache.canCache(RoundedSize)) { // Note that cached blocks may have aligned address already. Thus we simply
// pass the required size (`Size` + `getHeadersSize()`) to do cache look up.
const uptr MinNeededSizeForCache = roundUp(Size + getHeadersSize(), PageSize);
if (Alignment < PageSize && Cache.canCache(MinNeededSizeForCache)) {
LargeBlock::Header *H; LargeBlock::Header *H;
bool Zeroed; bool Zeroed;
if (Cache.retrieve(Options, Size, Alignment, &H, &Zeroed)) { if (Cache.retrieve(Options, Size, Alignment, getHeadersSize(), &H,
&Zeroed)) {
const uptr BlockEnd = H->CommitBase + H->CommitSize; const uptr BlockEnd = H->CommitBase + H->CommitSize;
if (BlockEndPtr) if (BlockEndPtr)
*BlockEndPtr = BlockEnd; *BlockEndPtr = BlockEnd;
@ -545,6 +590,7 @@ void *MapAllocator<Config>::allocate(Options Options, uptr Size, uptr Alignment,
ScopedLock L(Mutex); ScopedLock L(Mutex);
InUseBlocks.push_back(H); InUseBlocks.push_back(H);
AllocatedBytes += H->CommitSize; AllocatedBytes += H->CommitSize;
FragmentedBytes += H->MemMap.getCapacity() - H->CommitSize;
NumberOfAllocs++; NumberOfAllocs++;
Stats.add(StatAllocated, H->CommitSize); Stats.add(StatAllocated, H->CommitSize);
Stats.add(StatMapped, H->MemMap.getCapacity()); Stats.add(StatMapped, H->MemMap.getCapacity());
@ -553,16 +599,22 @@ void *MapAllocator<Config>::allocate(Options Options, uptr Size, uptr Alignment,
} }
} }
uptr RoundedSize =
roundUp(roundUp(Size, Alignment) + getHeadersSize(), PageSize);
if (Alignment > PageSize)
RoundedSize += Alignment - PageSize;
ReservedMemoryT ReservedMemory; ReservedMemoryT ReservedMemory;
const uptr MapSize = RoundedSize + 2 * PageSize; const uptr MapSize = RoundedSize + 2 * PageSize;
ReservedMemory.create(/*Addr=*/0U, MapSize, nullptr, MAP_ALLOWNOMEM); if (UNLIKELY(!ReservedMemory.create(/*Addr=*/0U, MapSize, nullptr,
MAP_ALLOWNOMEM))) {
return nullptr;
}
// Take the entire ownership of reserved region. // Take the entire ownership of reserved region.
MemMapT MemMap = ReservedMemory.dispatch(ReservedMemory.getBase(), MemMapT MemMap = ReservedMemory.dispatch(ReservedMemory.getBase(),
ReservedMemory.getCapacity()); ReservedMemory.getCapacity());
uptr MapBase = MemMap.getBase(); uptr MapBase = MemMap.getBase();
if (UNLIKELY(!MapBase))
return nullptr;
uptr CommitBase = MapBase + PageSize; uptr CommitBase = MapBase + PageSize;
uptr MapEnd = MapBase + MapSize; uptr MapEnd = MapBase + MapSize;
@ -592,9 +644,12 @@ void *MapAllocator<Config>::allocate(Options Options, uptr Size, uptr Alignment,
const uptr CommitSize = MapEnd - PageSize - CommitBase; const uptr CommitSize = MapEnd - PageSize - CommitBase;
const uptr AllocPos = roundDown(CommitBase + CommitSize - Size, Alignment); const uptr AllocPos = roundDown(CommitBase + CommitSize - Size, Alignment);
mapSecondary<Config>(Options, CommitBase, CommitSize, AllocPos, 0, MemMap); if (!mapSecondary<Config>(Options, CommitBase, CommitSize, AllocPos, 0,
const uptr HeaderPos = MemMap)) {
AllocPos - Chunk::getHeaderSize() - LargeBlock::getHeaderSize(); MemMap.unmap(MemMap.getBase(), MemMap.getCapacity());
return nullptr;
}
const uptr HeaderPos = AllocPos - getHeadersSize();
LargeBlock::Header *H = reinterpret_cast<LargeBlock::Header *>( LargeBlock::Header *H = reinterpret_cast<LargeBlock::Header *>(
LargeBlock::addHeaderTag<Config>(HeaderPos)); LargeBlock::addHeaderTag<Config>(HeaderPos));
if (useMemoryTagging<Config>(Options)) if (useMemoryTagging<Config>(Options))
@ -609,6 +664,7 @@ void *MapAllocator<Config>::allocate(Options Options, uptr Size, uptr Alignment,
ScopedLock L(Mutex); ScopedLock L(Mutex);
InUseBlocks.push_back(H); InUseBlocks.push_back(H);
AllocatedBytes += CommitSize; AllocatedBytes += CommitSize;
FragmentedBytes += H->MemMap.getCapacity() - CommitSize;
if (LargestSize < CommitSize) if (LargestSize < CommitSize)
LargestSize = CommitSize; LargestSize = CommitSize;
NumberOfAllocs++; NumberOfAllocs++;
@ -619,7 +675,7 @@ void *MapAllocator<Config>::allocate(Options Options, uptr Size, uptr Alignment,
} }
template <typename Config> template <typename Config>
void MapAllocator<Config>::deallocate(Options Options, void *Ptr) void MapAllocator<Config>::deallocate(const Options &Options, void *Ptr)
EXCLUDES(Mutex) { EXCLUDES(Mutex) {
LargeBlock::Header *H = LargeBlock::getHeader<Config>(Ptr); LargeBlock::Header *H = LargeBlock::getHeader<Config>(Ptr);
const uptr CommitSize = H->CommitSize; const uptr CommitSize = H->CommitSize;
@ -627,6 +683,7 @@ void MapAllocator<Config>::deallocate(Options Options, void *Ptr)
ScopedLock L(Mutex); ScopedLock L(Mutex);
InUseBlocks.remove(H); InUseBlocks.remove(H);
FreedBytes += CommitSize; FreedBytes += CommitSize;
FragmentedBytes -= H->MemMap.getCapacity() - CommitSize;
NumberOfFrees++; NumberOfFrees++;
Stats.sub(StatAllocated, CommitSize); Stats.sub(StatAllocated, CommitSize);
Stats.sub(StatMapped, H->MemMap.getCapacity()); Stats.sub(StatMapped, H->MemMap.getCapacity());
@ -638,10 +695,11 @@ template <typename Config>
void MapAllocator<Config>::getStats(ScopedString *Str) EXCLUDES(Mutex) { void MapAllocator<Config>::getStats(ScopedString *Str) EXCLUDES(Mutex) {
ScopedLock L(Mutex); ScopedLock L(Mutex);
Str->append("Stats: MapAllocator: allocated %u times (%zuK), freed %u times " Str->append("Stats: MapAllocator: allocated %u times (%zuK), freed %u times "
"(%zuK), remains %u (%zuK) max %zuM\n", "(%zuK), remains %u (%zuK) max %zuM, Fragmented %zuK\n",
NumberOfAllocs, AllocatedBytes >> 10, NumberOfFrees, NumberOfAllocs, AllocatedBytes >> 10, NumberOfFrees,
FreedBytes >> 10, NumberOfAllocs - NumberOfFrees, FreedBytes >> 10, NumberOfAllocs - NumberOfFrees,
(AllocatedBytes - FreedBytes) >> 10, LargestSize >> 20); (AllocatedBytes - FreedBytes) >> 10, LargestSize >> 20,
FragmentedBytes >> 10);
Cache.getStats(Str); Cache.getStats(Str);
} }

View file

@ -254,7 +254,7 @@ struct AndroidSizeClassConfig {
static const u16 MaxNumCachedHint = 13; static const u16 MaxNumCachedHint = 13;
static const uptr MaxBytesCachedLog = 13; static const uptr MaxBytesCachedLog = 13;
static constexpr u32 Classes[] = { static constexpr uptr Classes[] = {
0x00020, 0x00030, 0x00040, 0x00050, 0x00060, 0x00070, 0x00090, 0x000b0, 0x00020, 0x00030, 0x00040, 0x00050, 0x00060, 0x00070, 0x00090, 0x000b0,
0x000c0, 0x000e0, 0x00120, 0x00160, 0x001c0, 0x00250, 0x00320, 0x00450, 0x000c0, 0x000e0, 0x00120, 0x00160, 0x001c0, 0x00250, 0x00320, 0x00450,
0x00670, 0x00830, 0x00a10, 0x00c30, 0x01010, 0x01210, 0x01bd0, 0x02210, 0x00670, 0x00830, 0x00a10, 0x00c30, 0x01010, 0x01210, 0x01bd0, 0x02210,
@ -269,7 +269,7 @@ struct AndroidSizeClassConfig {
static const u16 MaxNumCachedHint = 14; static const u16 MaxNumCachedHint = 14;
static const uptr MaxBytesCachedLog = 13; static const uptr MaxBytesCachedLog = 13;
static constexpr u32 Classes[] = { static constexpr uptr Classes[] = {
0x00020, 0x00030, 0x00040, 0x00050, 0x00060, 0x00070, 0x00080, 0x00090, 0x00020, 0x00030, 0x00040, 0x00050, 0x00060, 0x00070, 0x00080, 0x00090,
0x000a0, 0x000b0, 0x000c0, 0x000e0, 0x000f0, 0x00110, 0x00120, 0x00130, 0x000a0, 0x000b0, 0x000c0, 0x000e0, 0x000f0, 0x00110, 0x00120, 0x00130,
0x00150, 0x00160, 0x00170, 0x00190, 0x001d0, 0x00210, 0x00240, 0x002a0, 0x00150, 0x00160, 0x00170, 0x00190, 0x001d0, 0x00210, 0x00240, 0x002a0,
@ -289,28 +289,6 @@ typedef TableSizeClassMap<AndroidSizeClassConfig> AndroidSizeClassMap;
static_assert(AndroidSizeClassMap::usesCompressedLSBFormat(), ""); static_assert(AndroidSizeClassMap::usesCompressedLSBFormat(), "");
#endif #endif
struct SvelteSizeClassConfig {
#if SCUDO_WORDSIZE == 64U
static const uptr NumBits = 4;
static const uptr MinSizeLog = 4;
static const uptr MidSizeLog = 8;
static const uptr MaxSizeLog = 14;
static const u16 MaxNumCachedHint = 13;
static const uptr MaxBytesCachedLog = 10;
static const uptr SizeDelta = Chunk::getHeaderSize();
#else
static const uptr NumBits = 4;
static const uptr MinSizeLog = 3;
static const uptr MidSizeLog = 7;
static const uptr MaxSizeLog = 14;
static const u16 MaxNumCachedHint = 14;
static const uptr MaxBytesCachedLog = 10;
static const uptr SizeDelta = Chunk::getHeaderSize();
#endif
};
typedef FixedSizeClassMap<SvelteSizeClassConfig> SvelteSizeClassMap;
struct TrustySizeClassConfig { struct TrustySizeClassConfig {
static const uptr NumBits = 1; static const uptr NumBits = 1;
static const uptr MinSizeLog = 5; static const uptr MinSizeLog = 5;

View file

@ -62,8 +62,7 @@ class StackDepot {
// This is achieved by re-checking the hash of the stack trace before // This is achieved by re-checking the hash of the stack trace before
// returning the trace. // returning the trace.
#ifdef SCUDO_FUZZ #if SCUDO_SMALL_STACK_DEPOT
// Use smaller table sizes for fuzzing in order to reduce input size.
static const uptr TabBits = 4; static const uptr TabBits = 4;
#else #else
static const uptr TabBits = 16; static const uptr TabBits = 16;
@ -72,7 +71,7 @@ class StackDepot {
static const uptr TabMask = TabSize - 1; static const uptr TabMask = TabSize - 1;
atomic_u32 Tab[TabSize] = {}; atomic_u32 Tab[TabSize] = {};
#ifdef SCUDO_FUZZ #if SCUDO_SMALL_STACK_DEPOT
static const uptr RingBits = 4; static const uptr RingBits = 4;
#else #else
static const uptr RingBits = 19; static const uptr RingBits = 19;

View file

@ -15,11 +15,15 @@ set(SCUDO_UNITTEST_CFLAGS
-DGTEST_HAS_RTTI=0 -DGTEST_HAS_RTTI=0
-g -g
# Extra flags for the C++ tests # Extra flags for the C++ tests
-Wconversion
# TODO(kostyak): find a way to make -fsized-deallocation work # TODO(kostyak): find a way to make -fsized-deallocation work
-Wno-mismatched-new-delete) -Wno-mismatched-new-delete)
if(COMPILER_RT_DEBUG) if(COMPILER_RT_DEBUG)
list(APPEND SCUDO_UNITTEST_CFLAGS -DSCUDO_DEBUG=1) list(APPEND SCUDO_UNITTEST_CFLAGS -DSCUDO_DEBUG=1 -DSCUDO_ENABLE_HOOKS=1)
if (NOT FUCHSIA)
list(APPEND SCUDO_UNITTEST_CFLAGS -DSCUDO_ENABLE_HOOKS_TESTS=1)
endif()
endif() endif()
if(ANDROID) if(ANDROID)
@ -92,6 +96,7 @@ set(SCUDO_UNIT_TEST_SOURCES
chunk_test.cpp chunk_test.cpp
combined_test.cpp combined_test.cpp
common_test.cpp common_test.cpp
condition_variable_test.cpp
flags_test.cpp flags_test.cpp
list_test.cpp list_test.cpp
map_test.cpp map_test.cpp
@ -137,11 +142,3 @@ set(SCUDO_CXX_UNIT_TEST_SOURCES
add_scudo_unittest(ScudoCxxUnitTest add_scudo_unittest(ScudoCxxUnitTest
SOURCES ${SCUDO_CXX_UNIT_TEST_SOURCES} SOURCES ${SCUDO_CXX_UNIT_TEST_SOURCES}
ADDITIONAL_RTOBJECTS RTScudoStandaloneCWrappers RTScudoStandaloneCxxWrappers) ADDITIONAL_RTOBJECTS RTScudoStandaloneCWrappers RTScudoStandaloneCxxWrappers)
set(SCUDO_HOOKS_UNIT_TEST_SOURCES
scudo_hooks_test.cpp
scudo_unit_test_main.cpp
)
add_scudo_unittest(ScudoHooksUnitTest
SOURCES ${SCUDO_HOOKS_UNIT_TEST_SOURCES})

View file

@ -37,29 +37,6 @@ TEST(ScudoChunkDeathTest, ChunkBasic) {
free(Block); free(Block);
} }
TEST(ScudoChunkTest, ChunkCmpXchg) {
initChecksum();
const scudo::uptr Size = 0x100U;
scudo::Chunk::UnpackedHeader OldHeader = {};
OldHeader.OriginOrWasZeroed = scudo::Chunk::Origin::Malloc;
OldHeader.ClassId = 0x42U;
OldHeader.SizeOrUnusedBytes = Size;
OldHeader.State = scudo::Chunk::State::Allocated;
void *Block = malloc(HeaderSize + Size);
void *P = reinterpret_cast<void *>(reinterpret_cast<scudo::uptr>(Block) +
HeaderSize);
scudo::Chunk::storeHeader(Cookie, P, &OldHeader);
memset(P, 'A', Size);
scudo::Chunk::UnpackedHeader NewHeader = OldHeader;
NewHeader.State = scudo::Chunk::State::Quarantined;
scudo::Chunk::compareExchangeHeader(Cookie, P, &NewHeader, &OldHeader);
NewHeader = {};
EXPECT_TRUE(scudo::Chunk::isValid(Cookie, P, &NewHeader));
EXPECT_EQ(NewHeader.State, scudo::Chunk::State::Quarantined);
EXPECT_FALSE(scudo::Chunk::isValid(InvalidCookie, P, &NewHeader));
free(Block);
}
TEST(ScudoChunkDeathTest, CorruptHeader) { TEST(ScudoChunkDeathTest, CorruptHeader) {
initChecksum(); initChecksum();
const scudo::uptr Size = 0x100U; const scudo::uptr Size = 0x100U;

View file

@ -12,8 +12,11 @@
#include "allocator_config.h" #include "allocator_config.h"
#include "chunk.h" #include "chunk.h"
#include "combined.h" #include "combined.h"
#include "condition_variable.h"
#include "mem_map.h" #include "mem_map.h"
#include "size_class_map.h"
#include <algorithm>
#include <condition_variable> #include <condition_variable>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@ -54,7 +57,7 @@ void checkMemoryTaggingMaybe(AllocatorT *Allocator, void *P, scudo::uptr Size,
EXPECT_DEATH( EXPECT_DEATH(
{ {
disableDebuggerdMaybe(); disableDebuggerdMaybe();
reinterpret_cast<char *>(P)[-1] = 0xaa; reinterpret_cast<char *>(P)[-1] = 'A';
}, },
""); "");
if (isPrimaryAllocation<AllocatorT>(Size, Alignment) if (isPrimaryAllocation<AllocatorT>(Size, Alignment)
@ -63,7 +66,7 @@ void checkMemoryTaggingMaybe(AllocatorT *Allocator, void *P, scudo::uptr Size,
EXPECT_DEATH( EXPECT_DEATH(
{ {
disableDebuggerdMaybe(); disableDebuggerdMaybe();
reinterpret_cast<char *>(P)[Size] = 0xaa; reinterpret_cast<char *>(P)[Size] = 'A';
}, },
""); "");
} }
@ -78,14 +81,70 @@ template <typename Config> struct TestAllocator : scudo::Allocator<Config> {
} }
~TestAllocator() { this->unmapTestOnly(); } ~TestAllocator() { this->unmapTestOnly(); }
void *operator new(size_t size) { void *operator new(size_t size);
void *p = nullptr; void operator delete(void *ptr);
EXPECT_EQ(0, posix_memalign(&p, alignof(TestAllocator), size)); };
return p;
constexpr size_t kMaxAlign = std::max({
alignof(scudo::Allocator<scudo::DefaultConfig>),
#if SCUDO_CAN_USE_PRIMARY64
alignof(scudo::Allocator<scudo::FuchsiaConfig>),
#endif
alignof(scudo::Allocator<scudo::AndroidConfig>)
});
#if SCUDO_RISCV64
// The allocator is over 4MB large. Rather than creating an instance of this on
// the heap, keep it in a global storage to reduce fragmentation from having to
// mmap this at the start of every test.
struct TestAllocatorStorage {
static constexpr size_t kMaxSize = std::max({
sizeof(scudo::Allocator<scudo::DefaultConfig>),
#if SCUDO_CAN_USE_PRIMARY64
sizeof(scudo::Allocator<scudo::FuchsiaConfig>),
#endif
sizeof(scudo::Allocator<scudo::AndroidConfig>)
});
// To alleviate some problem, let's skip the thread safety analysis here.
static void *get(size_t size) NO_THREAD_SAFETY_ANALYSIS {
CHECK(size <= kMaxSize &&
"Allocation size doesn't fit in the allocator storage");
M.lock();
return AllocatorStorage;
} }
void operator delete(void *ptr) { free(ptr); } static void release(void *ptr) NO_THREAD_SAFETY_ANALYSIS {
M.assertHeld();
M.unlock();
ASSERT_EQ(ptr, AllocatorStorage);
}
static scudo::HybridMutex M;
static uint8_t AllocatorStorage[kMaxSize];
}; };
scudo::HybridMutex TestAllocatorStorage::M;
alignas(kMaxAlign) uint8_t TestAllocatorStorage::AllocatorStorage[kMaxSize];
#else
struct TestAllocatorStorage {
static void *get(size_t size) NO_THREAD_SAFETY_ANALYSIS {
void *p = nullptr;
EXPECT_EQ(0, posix_memalign(&p, kMaxAlign, size));
return p;
}
static void release(void *ptr) NO_THREAD_SAFETY_ANALYSIS { free(ptr); }
};
#endif
template <typename Config>
void *TestAllocator<Config>::operator new(size_t size) {
return TestAllocatorStorage::get(size);
}
template <typename Config>
void TestAllocator<Config>::operator delete(void *ptr) {
TestAllocatorStorage::release(ptr);
}
template <class TypeParam> struct ScudoCombinedTest : public Test { template <class TypeParam> struct ScudoCombinedTest : public Test {
ScudoCombinedTest() { ScudoCombinedTest() {
@ -107,15 +166,60 @@ template <class TypeParam> struct ScudoCombinedTest : public Test {
template <typename T> using ScudoCombinedDeathTest = ScudoCombinedTest<T>; template <typename T> using ScudoCombinedDeathTest = ScudoCombinedTest<T>;
namespace scudo {
struct TestConditionVariableConfig {
static const bool MaySupportMemoryTagging = true;
template <class A>
using TSDRegistryT =
scudo::TSDRegistrySharedT<A, 8U, 4U>; // Shared, max 8 TSDs.
struct Primary {
using SizeClassMap = scudo::AndroidSizeClassMap;
#if SCUDO_CAN_USE_PRIMARY64
static const scudo::uptr RegionSizeLog = 28U;
typedef scudo::u32 CompactPtrT;
static const scudo::uptr CompactPtrScale = SCUDO_MIN_ALIGNMENT_LOG;
static const scudo::uptr GroupSizeLog = 20U;
static const bool EnableRandomOffset = true;
static const scudo::uptr MapSizeIncrement = 1UL << 18;
#else
static const scudo::uptr RegionSizeLog = 18U;
static const scudo::uptr GroupSizeLog = 18U;
typedef scudo::uptr CompactPtrT;
#endif
static const scudo::s32 MinReleaseToOsIntervalMs = 1000;
static const scudo::s32 MaxReleaseToOsIntervalMs = 1000;
static const bool UseConditionVariable = true;
#if SCUDO_LINUX
using ConditionVariableT = scudo::ConditionVariableLinux;
#else
using ConditionVariableT = scudo::ConditionVariableDummy;
#endif
};
#if SCUDO_CAN_USE_PRIMARY64
template <typename Config>
using PrimaryT = scudo::SizeClassAllocator64<Config>;
#else
template <typename Config>
using PrimaryT = scudo::SizeClassAllocator32<Config>;
#endif
struct Secondary {
template <typename Config>
using CacheT = scudo::MapAllocatorNoCache<Config>;
};
template <typename Config> using SecondaryT = scudo::MapAllocator<Config>;
};
} // namespace scudo
#if SCUDO_FUCHSIA #if SCUDO_FUCHSIA
#define SCUDO_TYPED_TEST_ALL_TYPES(FIXTURE, NAME) \ #define SCUDO_TYPED_TEST_ALL_TYPES(FIXTURE, NAME) \
SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, AndroidSvelteConfig) \
SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, FuchsiaConfig) SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, FuchsiaConfig)
#else #else
#define SCUDO_TYPED_TEST_ALL_TYPES(FIXTURE, NAME) \ #define SCUDO_TYPED_TEST_ALL_TYPES(FIXTURE, NAME) \
SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, AndroidSvelteConfig) \
SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, DefaultConfig) \ SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, DefaultConfig) \
SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, AndroidConfig) SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, AndroidConfig) \
SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TestConditionVariableConfig)
#endif #endif
#define SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TYPE) \ #define SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TYPE) \
@ -170,6 +274,7 @@ void ScudoCombinedTest<Config>::BasicTest(scudo::uptr SizeLog) {
} }
Allocator->printStats(); Allocator->printStats();
Allocator->printFragmentationInfo();
} }
#define SCUDO_MAKE_BASIC_TEST(SizeLog) \ #define SCUDO_MAKE_BASIC_TEST(SizeLog) \
@ -209,7 +314,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ZeroContents) {
void *P = Allocator->allocate(Size, Origin, 1U << MinAlignLog, true); void *P = Allocator->allocate(Size, Origin, 1U << MinAlignLog, true);
EXPECT_NE(P, nullptr); EXPECT_NE(P, nullptr);
for (scudo::uptr I = 0; I < Size; I++) for (scudo::uptr I = 0; I < Size; I++)
ASSERT_EQ((reinterpret_cast<char *>(P))[I], 0); ASSERT_EQ((reinterpret_cast<char *>(P))[I], '\0');
memset(P, 0xaa, Size); memset(P, 0xaa, Size);
Allocator->deallocate(P, Origin, Size); Allocator->deallocate(P, Origin, Size);
} }
@ -227,7 +332,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ZeroFill) {
void *P = Allocator->allocate(Size, Origin, 1U << MinAlignLog, false); void *P = Allocator->allocate(Size, Origin, 1U << MinAlignLog, false);
EXPECT_NE(P, nullptr); EXPECT_NE(P, nullptr);
for (scudo::uptr I = 0; I < Size; I++) for (scudo::uptr I = 0; I < Size; I++)
ASSERT_EQ((reinterpret_cast<char *>(P))[I], 0); ASSERT_EQ((reinterpret_cast<char *>(P))[I], '\0');
memset(P, 0xaa, Size); memset(P, 0xaa, Size);
Allocator->deallocate(P, Origin, Size); Allocator->deallocate(P, Origin, Size);
} }
@ -286,7 +391,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ReallocateLargeIncreasing) {
// we preserve the data in the process. // we preserve the data in the process.
scudo::uptr Size = 16; scudo::uptr Size = 16;
void *P = Allocator->allocate(Size, Origin); void *P = Allocator->allocate(Size, Origin);
const char Marker = 0xab; const char Marker = 'A';
memset(P, Marker, Size); memset(P, Marker, Size);
while (Size < TypeParam::Primary::SizeClassMap::MaxSize * 4) { while (Size < TypeParam::Primary::SizeClassMap::MaxSize * 4) {
void *NewP = Allocator->reallocate(P, Size * 2); void *NewP = Allocator->reallocate(P, Size * 2);
@ -308,7 +413,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ReallocateLargeDecreasing) {
scudo::uptr Size = TypeParam::Primary::SizeClassMap::MaxSize * 2; scudo::uptr Size = TypeParam::Primary::SizeClassMap::MaxSize * 2;
const scudo::uptr DataSize = 2048U; const scudo::uptr DataSize = 2048U;
void *P = Allocator->allocate(Size, Origin); void *P = Allocator->allocate(Size, Origin);
const char Marker = 0xab; const char Marker = 'A';
memset(P, Marker, scudo::Min(Size, DataSize)); memset(P, Marker, scudo::Min(Size, DataSize));
while (Size > 1U) { while (Size > 1U) {
Size /= 2U; Size /= 2U;
@ -331,7 +436,7 @@ SCUDO_TYPED_TEST(ScudoCombinedDeathTest, ReallocateSame) {
constexpr scudo::uptr ReallocSize = constexpr scudo::uptr ReallocSize =
TypeParam::Primary::SizeClassMap::MaxSize - 64; TypeParam::Primary::SizeClassMap::MaxSize - 64;
void *P = Allocator->allocate(ReallocSize, Origin); void *P = Allocator->allocate(ReallocSize, Origin);
const char Marker = 0xab; const char Marker = 'A';
memset(P, Marker, ReallocSize); memset(P, Marker, ReallocSize);
for (scudo::sptr Delta = -32; Delta < 32; Delta += 8) { for (scudo::sptr Delta = -32; Delta < 32; Delta += 8) {
const scudo::uptr NewSize = const scudo::uptr NewSize =
@ -388,7 +493,7 @@ SCUDO_TYPED_TEST(ScudoCombinedDeathTest, UseAfterFree) {
disableDebuggerdMaybe(); disableDebuggerdMaybe();
void *P = Allocator->allocate(Size, Origin); void *P = Allocator->allocate(Size, Origin);
Allocator->deallocate(P, Origin); Allocator->deallocate(P, Origin);
reinterpret_cast<char *>(P)[0] = 0xaa; reinterpret_cast<char *>(P)[0] = 'A';
}, },
""); "");
EXPECT_DEATH( EXPECT_DEATH(
@ -396,7 +501,7 @@ SCUDO_TYPED_TEST(ScudoCombinedDeathTest, UseAfterFree) {
disableDebuggerdMaybe(); disableDebuggerdMaybe();
void *P = Allocator->allocate(Size, Origin); void *P = Allocator->allocate(Size, Origin);
Allocator->deallocate(P, Origin); Allocator->deallocate(P, Origin);
reinterpret_cast<char *>(P)[Size - 1] = 0xaa; reinterpret_cast<char *>(P)[Size - 1] = 'A';
}, },
""); "");
} }
@ -408,15 +513,15 @@ SCUDO_TYPED_TEST(ScudoCombinedDeathTest, DisableMemoryTagging) {
if (Allocator->useMemoryTaggingTestOnly()) { if (Allocator->useMemoryTaggingTestOnly()) {
// Check that disabling memory tagging works correctly. // Check that disabling memory tagging works correctly.
void *P = Allocator->allocate(2048, Origin); void *P = Allocator->allocate(2048, Origin);
EXPECT_DEATH(reinterpret_cast<char *>(P)[2048] = 0xaa, ""); EXPECT_DEATH(reinterpret_cast<char *>(P)[2048] = 'A', "");
scudo::ScopedDisableMemoryTagChecks NoTagChecks; scudo::ScopedDisableMemoryTagChecks NoTagChecks;
Allocator->disableMemoryTagging(); Allocator->disableMemoryTagging();
reinterpret_cast<char *>(P)[2048] = 0xaa; reinterpret_cast<char *>(P)[2048] = 'A';
Allocator->deallocate(P, Origin); Allocator->deallocate(P, Origin);
P = Allocator->allocate(2048, Origin); P = Allocator->allocate(2048, Origin);
EXPECT_EQ(scudo::untagPointer(P), P); EXPECT_EQ(scudo::untagPointer(P), P);
reinterpret_cast<char *>(P)[2048] = 0xaa; reinterpret_cast<char *>(P)[2048] = 'A';
Allocator->deallocate(P, Origin); Allocator->deallocate(P, Origin);
Allocator->releaseToOS(scudo::ReleaseToOS::Force); Allocator->releaseToOS(scudo::ReleaseToOS::Force);
@ -456,6 +561,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, CacheDrain) NO_THREAD_SAFETY_ANALYSIS {
bool UnlockRequired; bool UnlockRequired;
auto *TSD = Allocator->getTSDRegistry()->getTSDAndLock(&UnlockRequired); auto *TSD = Allocator->getTSDRegistry()->getTSDAndLock(&UnlockRequired);
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
EXPECT_TRUE(!TSD->getCache().isEmpty()); EXPECT_TRUE(!TSD->getCache().isEmpty());
TSD->getCache().drain(); TSD->getCache().drain();
EXPECT_TRUE(TSD->getCache().isEmpty()); EXPECT_TRUE(TSD->getCache().isEmpty());
@ -480,6 +586,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ForceCacheDrain) NO_THREAD_SAFETY_ANALYSIS {
bool UnlockRequired; bool UnlockRequired;
auto *TSD = Allocator->getTSDRegistry()->getTSDAndLock(&UnlockRequired); auto *TSD = Allocator->getTSDRegistry()->getTSDAndLock(&UnlockRequired);
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
EXPECT_TRUE(TSD->getCache().isEmpty()); EXPECT_TRUE(TSD->getCache().isEmpty());
EXPECT_EQ(TSD->getQuarantineCache().getSize(), 0U); EXPECT_EQ(TSD->getQuarantineCache().getSize(), 0U);
EXPECT_TRUE(Allocator->getQuarantine()->isEmpty()); EXPECT_TRUE(Allocator->getQuarantine()->isEmpty());
@ -723,7 +830,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, DisableMemInit) {
for (unsigned I = 0; I != Ptrs.size(); ++I) { for (unsigned I = 0; I != Ptrs.size(); ++I) {
Ptrs[I] = Allocator->allocate(Size, Origin, 1U << MinAlignLog, true); Ptrs[I] = Allocator->allocate(Size, Origin, 1U << MinAlignLog, true);
for (scudo::uptr J = 0; J < Size; ++J) for (scudo::uptr J = 0; J < Size; ++J)
ASSERT_EQ((reinterpret_cast<char *>(Ptrs[I]))[J], 0); ASSERT_EQ((reinterpret_cast<char *>(Ptrs[I]))[J], '\0');
} }
} }
@ -786,6 +893,7 @@ TEST(ScudoCombinedTest, BasicTrustyConfig) {
bool UnlockRequired; bool UnlockRequired;
auto *TSD = Allocator->getTSDRegistry()->getTSDAndLock(&UnlockRequired); auto *TSD = Allocator->getTSDRegistry()->getTSDAndLock(&UnlockRequired);
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
TSD->getCache().drain(); TSD->getCache().drain();
Allocator->releaseToOS(scudo::ReleaseToOS::Force); Allocator->releaseToOS(scudo::ReleaseToOS::Force);
@ -793,41 +901,3 @@ TEST(ScudoCombinedTest, BasicTrustyConfig) {
#endif #endif
#endif #endif
#if SCUDO_LINUX
SCUDO_TYPED_TEST(ScudoCombinedTest, SoftRssLimit) {
auto *Allocator = this->Allocator.get();
Allocator->setRssLimitsTestOnly(1, 0, true);
size_t Megabyte = 1024 * 1024;
size_t ChunkSize = 16;
size_t Error = 256;
std::vector<void *> Ptrs;
for (size_t index = 0; index < Megabyte + Error; index += ChunkSize) {
void *Ptr = Allocator->allocate(ChunkSize, Origin);
Ptrs.push_back(Ptr);
}
EXPECT_EQ(nullptr, Allocator->allocate(ChunkSize, Origin));
for (void *Ptr : Ptrs)
Allocator->deallocate(Ptr, Origin);
}
SCUDO_TYPED_TEST(ScudoCombinedTest, HardRssLimit) {
auto *Allocator = this->Allocator.get();
Allocator->setRssLimitsTestOnly(0, 1, false);
size_t Megabyte = 1024 * 1024;
EXPECT_DEATH(
{
disableDebuggerdMaybe();
Allocator->allocate(Megabyte, Origin);
},
"");
}
#endif

View file

@ -72,27 +72,4 @@ TEST(ScudoCommonTest, Zeros) {
MemMap.unmap(MemMap.getBase(), Size); MemMap.unmap(MemMap.getBase(), Size);
} }
#if 0
// This test is temorarily disabled because it may not work as expected. E.g.,
// it doesn't dirty the pages so the pages may not be commited and it may only
// work on the single thread environment. As a result, this test is flaky and is
// impacting many test scenarios.
TEST(ScudoCommonTest, GetRssFromBuffer) {
constexpr int64_t AllocSize = 10000000;
constexpr int64_t Error = 3000000;
constexpr size_t Runs = 10;
int64_t Rss = scudo::GetRSS();
EXPECT_GT(Rss, 0);
std::vector<std::unique_ptr<char[]>> Allocs(Runs);
for (auto &Alloc : Allocs) {
Alloc.reset(new char[AllocSize]());
int64_t Prev = Rss;
Rss = scudo::GetRSS();
EXPECT_LE(std::abs(Rss - AllocSize - Prev), Error);
}
}
#endif
} // namespace scudo } // namespace scudo

View file

@ -0,0 +1,59 @@
//===-- condition_variable_test.cpp -----------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "tests/scudo_unit_test.h"
#include "common.h"
#include "condition_variable.h"
#include "mutex.h"
#include <thread>
template <typename ConditionVariableT> void simpleWaitAndNotifyAll() {
constexpr scudo::u32 NumThreads = 2;
constexpr scudo::u32 CounterMax = 1024;
std::thread Threads[NumThreads];
scudo::HybridMutex M;
ConditionVariableT CV;
CV.bindTestOnly(M);
scudo::u32 Counter = 0;
for (scudo::u32 I = 0; I < NumThreads; ++I) {
Threads[I] = std::thread(
[&](scudo::u32 Id) {
do {
scudo::ScopedLock L(M);
if (Counter % NumThreads != Id && Counter < CounterMax)
CV.wait(M);
if (Counter >= CounterMax) {
break;
} else {
++Counter;
CV.notifyAll(M);
}
} while (true);
},
I);
}
for (std::thread &T : Threads)
T.join();
EXPECT_EQ(Counter, CounterMax);
}
TEST(ScudoConditionVariableTest, DummyCVWaitAndNotifyAll) {
simpleWaitAndNotifyAll<scudo::ConditionVariableDummy>();
}
#ifdef SCUDO_LINUX
TEST(ScudoConditionVariableTest, LinuxCVWaitAndNotifyAll) {
simpleWaitAndNotifyAll<scudo::ConditionVariableLinux>();
}
#endif

View file

@ -120,7 +120,15 @@ TEST_F(MemtagTest, SelectRandomTag) {
uptr Tags = 0; uptr Tags = 0;
for (uptr I = 0; I < 100000; ++I) for (uptr I = 0; I < 100000; ++I)
Tags = Tags | (1u << extractTag(selectRandomTag(Ptr, 0))); Tags = Tags | (1u << extractTag(selectRandomTag(Ptr, 0)));
EXPECT_EQ(0xfffeull, Tags); // std::popcnt is C++20
int PopCnt = 0;
while (Tags) {
PopCnt += Tags & 1;
Tags >>= 1;
}
// Random tags are not always very random, and this test is not about PRNG
// quality. Anything above half would be satisfactory.
EXPECT_GE(PopCnt, 8);
} }
} }

View file

@ -8,6 +8,8 @@
#include "tests/scudo_unit_test.h" #include "tests/scudo_unit_test.h"
#include "allocator_config.h"
#include "condition_variable.h"
#include "primary32.h" #include "primary32.h"
#include "primary64.h" #include "primary64.h"
#include "size_class_map.h" #include "size_class_map.h"
@ -104,6 +106,34 @@ template <typename SizeClassMapT> struct TestConfig4 {
}; };
}; };
// This is the only test config that enables the condition variable.
template <typename SizeClassMapT> struct TestConfig5 {
static const bool MaySupportMemoryTagging = true;
struct Primary {
using SizeClassMap = SizeClassMapT;
#if defined(__mips__)
// Unable to allocate greater size on QEMU-user.
static const scudo::uptr RegionSizeLog = 23U;
#else
static const scudo::uptr RegionSizeLog = 24U;
#endif
static const scudo::s32 MinReleaseToOsIntervalMs = INT32_MIN;
static const scudo::s32 MaxReleaseToOsIntervalMs = INT32_MAX;
static const scudo::uptr CompactPtrScale = SCUDO_MIN_ALIGNMENT_LOG;
static const scudo::uptr GroupSizeLog = 18U;
typedef scudo::u32 CompactPtrT;
static const bool EnableRandomOffset = true;
static const scudo::uptr MapSizeIncrement = 1UL << 18;
static const bool UseConditionVariable = true;
#if SCUDO_LINUX
using ConditionVariableT = scudo::ConditionVariableLinux;
#else
using ConditionVariableT = scudo::ConditionVariableDummy;
#endif
};
};
template <template <typename> class BaseConfig, typename SizeClassMapT> template <template <typename> class BaseConfig, typename SizeClassMapT>
struct Config : public BaseConfig<SizeClassMapT> {}; struct Config : public BaseConfig<SizeClassMapT> {};
@ -142,7 +172,8 @@ struct ScudoPrimaryTest : public Test {};
SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TestConfig1) \ SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TestConfig1) \
SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TestConfig2) \ SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TestConfig2) \
SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TestConfig3) \ SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TestConfig3) \
SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TestConfig4) SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TestConfig4) \
SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TestConfig5)
#endif #endif
#define SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TYPE) \ #define SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TYPE) \
@ -206,7 +237,7 @@ struct SmallRegionsConfig {
// For the 32-bit one, it requires actually exhausting memory, so we skip it. // For the 32-bit one, it requires actually exhausting memory, so we skip it.
TEST(ScudoPrimaryTest, Primary64OOM) { TEST(ScudoPrimaryTest, Primary64OOM) {
using Primary = scudo::SizeClassAllocator64<SmallRegionsConfig>; using Primary = scudo::SizeClassAllocator64<SmallRegionsConfig>;
using TransferBatch = Primary::CacheT::TransferBatch; using TransferBatch = Primary::TransferBatchT;
Primary Allocator; Primary Allocator;
Allocator.init(/*ReleaseToOsInterval=*/-1); Allocator.init(/*ReleaseToOsInterval=*/-1);
typename Primary::CacheT Cache; typename Primary::CacheT Cache;
@ -232,8 +263,9 @@ TEST(ScudoPrimaryTest, Primary64OOM) {
while (!Batches.empty()) { while (!Batches.empty()) {
TransferBatch *B = Batches.back(); TransferBatch *B = Batches.back();
Batches.pop_back(); Batches.pop_back();
B->copyToArray(Blocks); const scudo::u16 Count = B->getCount();
Allocator.pushBlocks(&Cache, ClassId, Blocks, B->getCount()); B->moveToArray(Blocks);
Allocator.pushBlocks(&Cache, ClassId, Blocks, Count);
Cache.deallocate(Primary::SizeClassMap::BatchClassId, B); Cache.deallocate(Primary::SizeClassMap::BatchClassId, B);
} }
Cache.destroy(nullptr); Cache.destroy(nullptr);
@ -283,7 +315,7 @@ SCUDO_TYPED_TEST(ScudoPrimaryTest, PrimaryIterate) {
} }
SCUDO_TYPED_TEST(ScudoPrimaryTest, PrimaryThreaded) { SCUDO_TYPED_TEST(ScudoPrimaryTest, PrimaryThreaded) {
using Primary = TestAllocator<TypeParam, scudo::SvelteSizeClassMap>; using Primary = TestAllocator<TypeParam, scudo::Config::Primary::SizeClassMap>;
std::unique_ptr<Primary> Allocator(new Primary); std::unique_ptr<Primary> Allocator(new Primary);
Allocator->init(/*ReleaseToOsInterval=*/-1); Allocator->init(/*ReleaseToOsInterval=*/-1);
std::mutex Mutex; std::mutex Mutex;
@ -335,6 +367,7 @@ SCUDO_TYPED_TEST(ScudoPrimaryTest, PrimaryThreaded) {
Allocator->releaseToOS(scudo::ReleaseToOS::Force); Allocator->releaseToOS(scudo::ReleaseToOS::Force);
scudo::ScopedString Str; scudo::ScopedString Str;
Allocator->getStats(&Str); Allocator->getStats(&Str);
Allocator->getFragmentationInfo(&Str);
Str.output(); Str.output();
} }

View file

@ -23,17 +23,16 @@ TEST(ScudoReleaseTest, RegionPageMap) {
// Various valid counter's max values packed into one word. // Various valid counter's max values packed into one word.
scudo::RegionPageMap PageMap2N(1U, 1U, 1UL << I); scudo::RegionPageMap PageMap2N(1U, 1U, 1UL << I);
ASSERT_TRUE(PageMap2N.isAllocated()); ASSERT_TRUE(PageMap2N.isAllocated());
EXPECT_EQ(sizeof(scudo::uptr), PageMap2N.getBufferSize()); EXPECT_EQ(1U, PageMap2N.getBufferNumElements());
// Check the "all bit set" values too. // Check the "all bit set" values too.
scudo::RegionPageMap PageMap2N1_1(1U, 1U, ~0UL >> I); scudo::RegionPageMap PageMap2N1_1(1U, 1U, ~0UL >> I);
ASSERT_TRUE(PageMap2N1_1.isAllocated()); ASSERT_TRUE(PageMap2N1_1.isAllocated());
EXPECT_EQ(sizeof(scudo::uptr), PageMap2N1_1.getBufferSize()); EXPECT_EQ(1U, PageMap2N1_1.getBufferNumElements());
// Verify the packing ratio, the counter is Expected to be packed into the // Verify the packing ratio, the counter is Expected to be packed into the
// closest power of 2 bits. // closest power of 2 bits.
scudo::RegionPageMap PageMap(1U, SCUDO_WORDSIZE, 1UL << I); scudo::RegionPageMap PageMap(1U, SCUDO_WORDSIZE, 1UL << I);
ASSERT_TRUE(PageMap.isAllocated()); ASSERT_TRUE(PageMap.isAllocated());
EXPECT_EQ(sizeof(scudo::uptr) * scudo::roundUpPowerOfTwo(I + 1), EXPECT_EQ(scudo::roundUpPowerOfTwo(I + 1), PageMap.getBufferNumElements());
PageMap.getBufferSize());
} }
// Go through 1, 2, 4, 8, .. {32,64} bits per counter. // Go through 1, 2, 4, 8, .. {32,64} bits per counter.
@ -533,7 +532,8 @@ template <class SizeClassMap> void testReleasePartialRegion() {
ReleaseBase); ReleaseBase);
Partial.ensurePageMapAllocated(); Partial.ensurePageMapAllocated();
EXPECT_GE(Full.PageMap.getBufferSize(), Partial.PageMap.getBufferSize()); EXPECT_GE(Full.PageMap.getBufferNumElements(),
Partial.PageMap.getBufferNumElements());
} }
while (!FreeList.empty()) { while (!FreeList.empty()) {
@ -552,22 +552,16 @@ TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSAndroid) {
testReleaseFreeMemoryToOS<scudo::AndroidSizeClassMap>(); testReleaseFreeMemoryToOS<scudo::AndroidSizeClassMap>();
} }
TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSSvelte) {
testReleaseFreeMemoryToOS<scudo::SvelteSizeClassMap>();
}
TEST(ScudoReleaseTest, PageMapMarkRange) { TEST(ScudoReleaseTest, PageMapMarkRange) {
testPageMapMarkRange<scudo::DefaultSizeClassMap>(); testPageMapMarkRange<scudo::DefaultSizeClassMap>();
testPageMapMarkRange<scudo::AndroidSizeClassMap>(); testPageMapMarkRange<scudo::AndroidSizeClassMap>();
testPageMapMarkRange<scudo::FuchsiaSizeClassMap>(); testPageMapMarkRange<scudo::FuchsiaSizeClassMap>();
testPageMapMarkRange<scudo::SvelteSizeClassMap>();
} }
TEST(ScudoReleaseTest, ReleasePartialRegion) { TEST(ScudoReleaseTest, ReleasePartialRegion) {
testReleasePartialRegion<scudo::DefaultSizeClassMap>(); testReleasePartialRegion<scudo::DefaultSizeClassMap>();
testReleasePartialRegion<scudo::AndroidSizeClassMap>(); testReleasePartialRegion<scudo::AndroidSizeClassMap>();
testReleasePartialRegion<scudo::FuchsiaSizeClassMap>(); testReleasePartialRegion<scudo::FuchsiaSizeClassMap>();
testReleasePartialRegion<scudo::SvelteSizeClassMap>();
} }
template <class SizeClassMap> void testReleaseRangeWithSingleBlock() { template <class SizeClassMap> void testReleaseRangeWithSingleBlock() {
@ -630,31 +624,31 @@ TEST(ScudoReleaseTest, RangeReleaseRegionWithSingleBlock) {
testReleaseRangeWithSingleBlock<scudo::DefaultSizeClassMap>(); testReleaseRangeWithSingleBlock<scudo::DefaultSizeClassMap>();
testReleaseRangeWithSingleBlock<scudo::AndroidSizeClassMap>(); testReleaseRangeWithSingleBlock<scudo::AndroidSizeClassMap>();
testReleaseRangeWithSingleBlock<scudo::FuchsiaSizeClassMap>(); testReleaseRangeWithSingleBlock<scudo::FuchsiaSizeClassMap>();
testReleaseRangeWithSingleBlock<scudo::SvelteSizeClassMap>();
} }
TEST(ScudoReleaseTest, BufferPool) { TEST(ScudoReleaseTest, BufferPool) {
constexpr scudo::uptr StaticBufferCount = SCUDO_WORDSIZE - 1; constexpr scudo::uptr StaticBufferCount = SCUDO_WORDSIZE - 1;
constexpr scudo::uptr StaticBufferSize = 512U; constexpr scudo::uptr StaticBufferNumElements = 512U;
// Allocate the buffer pool on the heap because it is quite large (slightly // Allocate the buffer pool on the heap because it is quite large (slightly
// more than StaticBufferCount * StaticBufferSize * sizeof(uptr)) and it may // more than StaticBufferCount * StaticBufferNumElements * sizeof(uptr)) and
// not fit in the stack on some platforms. // it may not fit in the stack on some platforms.
using BufferPool = scudo::BufferPool<StaticBufferCount, StaticBufferSize>; using BufferPool =
scudo::BufferPool<StaticBufferCount, StaticBufferNumElements>;
std::unique_ptr<BufferPool> Pool(new BufferPool()); std::unique_ptr<BufferPool> Pool(new BufferPool());
std::vector<std::pair<scudo::uptr *, scudo::uptr>> Buffers; std::vector<BufferPool::Buffer> Buffers;
for (scudo::uptr I = 0; I < StaticBufferCount; ++I) { for (scudo::uptr I = 0; I < StaticBufferCount; ++I) {
scudo::uptr *P = Pool->getBuffer(StaticBufferSize); BufferPool::Buffer Buffer = Pool->getBuffer(StaticBufferNumElements);
EXPECT_TRUE(Pool->isStaticBufferTestOnly(P, StaticBufferSize)); EXPECT_TRUE(Pool->isStaticBufferTestOnly(Buffer));
Buffers.emplace_back(P, StaticBufferSize); Buffers.push_back(Buffer);
} }
// The static buffer is supposed to be used up. // The static buffer is supposed to be used up.
scudo::uptr *P = Pool->getBuffer(StaticBufferSize); BufferPool::Buffer Buffer = Pool->getBuffer(StaticBufferNumElements);
EXPECT_FALSE(Pool->isStaticBufferTestOnly(P, StaticBufferSize)); EXPECT_FALSE(Pool->isStaticBufferTestOnly(Buffer));
Pool->releaseBuffer(P, StaticBufferSize); Pool->releaseBuffer(Buffer);
for (auto &Buffer : Buffers) for (auto &Buffer : Buffers)
Pool->releaseBuffer(Buffer.first, Buffer.second); Pool->releaseBuffer(Buffer);
} }

View file

@ -23,7 +23,6 @@ TEST(ScudoReportDeathTest, Generic) {
EXPECT_DEATH(scudo::reportError("TEST123"), "Scudo ERROR.*TEST123"); EXPECT_DEATH(scudo::reportError("TEST123"), "Scudo ERROR.*TEST123");
EXPECT_DEATH(scudo::reportInvalidFlag("ABC", "DEF"), "Scudo ERROR.*ABC.*DEF"); EXPECT_DEATH(scudo::reportInvalidFlag("ABC", "DEF"), "Scudo ERROR.*ABC.*DEF");
EXPECT_DEATH(scudo::reportHeaderCorruption(P), "Scudo ERROR.*42424242"); EXPECT_DEATH(scudo::reportHeaderCorruption(P), "Scudo ERROR.*42424242");
EXPECT_DEATH(scudo::reportHeaderRace(P), "Scudo ERROR.*42424242");
EXPECT_DEATH(scudo::reportSanityCheckError("XYZ"), "Scudo ERROR.*XYZ"); EXPECT_DEATH(scudo::reportSanityCheckError("XYZ"), "Scudo ERROR.*XYZ");
EXPECT_DEATH(scudo::reportAlignmentTooBig(123, 456), "Scudo ERROR.*123.*456"); EXPECT_DEATH(scudo::reportAlignmentTooBig(123, 456), "Scudo ERROR.*123.*456");
EXPECT_DEATH(scudo::reportAllocationSizeTooBig(123, 456, 789), EXPECT_DEATH(scudo::reportAllocationSizeTooBig(123, 456, 789),

View file

@ -1,114 +0,0 @@
//===-- scudo_hooks_test.cpp ------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "tests/scudo_unit_test.h"
#include "allocator_config.h"
#include "combined.h"
namespace {
void *LastAllocatedPtr = nullptr;
size_t LastRequestSize = 0;
void *LastDeallocatedPtr = nullptr;
} // namespace
// Scudo defines weak symbols that can be defined by a client binary
// to register callbacks at key points in the allocation timeline. In
// order to enforce those invariants, we provide definitions that
// update some global state every time they are called, so that tests
// can inspect their effects. An unfortunate side effect of this
// setup is that because those symbols are part of the binary, they
// can't be selectively enabled; that means that they will get called
// on unrelated tests in the same compilation unit. To mitigate this
// issue, we insulate those tests in a separate compilation unit.
extern "C" {
__attribute__((visibility("default"))) void __scudo_allocate_hook(void *Ptr,
size_t Size) {
LastAllocatedPtr = Ptr;
LastRequestSize = Size;
}
__attribute__((visibility("default"))) void __scudo_deallocate_hook(void *Ptr) {
LastDeallocatedPtr = Ptr;
}
}
// Simple check that allocation callbacks, when registered, are called:
// 1) __scudo_allocate_hook is called when allocating.
// 2) __scudo_deallocate_hook is called when deallocating.
// 3) Both hooks are called when reallocating.
// 4) Neither are called for a no-op reallocation.
TEST(ScudoHooksTest, AllocateHooks) {
scudo::Allocator<scudo::DefaultConfig> Allocator;
constexpr scudo::uptr DefaultSize = 16U;
constexpr scudo::Chunk::Origin Origin = scudo::Chunk::Origin::Malloc;
// Simple allocation and deallocation.
{
LastAllocatedPtr = nullptr;
LastRequestSize = 0;
void *Ptr = Allocator.allocate(DefaultSize, Origin);
EXPECT_EQ(Ptr, LastAllocatedPtr);
EXPECT_EQ(DefaultSize, LastRequestSize);
LastDeallocatedPtr = nullptr;
Allocator.deallocate(Ptr, Origin);
EXPECT_EQ(Ptr, LastDeallocatedPtr);
}
// Simple no-op, same size reallocation.
{
void *Ptr = Allocator.allocate(DefaultSize, Origin);
LastAllocatedPtr = nullptr;
LastRequestSize = 0;
LastDeallocatedPtr = nullptr;
void *NewPtr = Allocator.reallocate(Ptr, DefaultSize);
EXPECT_EQ(Ptr, NewPtr);
EXPECT_EQ(nullptr, LastAllocatedPtr);
EXPECT_EQ(0U, LastRequestSize);
EXPECT_EQ(nullptr, LastDeallocatedPtr);
}
// Reallocation in increasing size classes. This ensures that at
// least one of the reallocations will be meaningful.
{
void *Ptr = Allocator.allocate(0, Origin);
for (scudo::uptr ClassId = 1U;
ClassId <= scudo::DefaultConfig::Primary::SizeClassMap::LargestClassId;
++ClassId) {
const scudo::uptr Size =
scudo::DefaultConfig::Primary::SizeClassMap::getSizeByClassId(
ClassId);
LastAllocatedPtr = nullptr;
LastRequestSize = 0;
LastDeallocatedPtr = nullptr;
void *NewPtr = Allocator.reallocate(Ptr, Size);
if (NewPtr != Ptr) {
EXPECT_EQ(NewPtr, LastAllocatedPtr);
EXPECT_EQ(Size, LastRequestSize);
EXPECT_EQ(Ptr, LastDeallocatedPtr);
} else {
EXPECT_EQ(nullptr, LastAllocatedPtr);
EXPECT_EQ(0U, LastRequestSize);
EXPECT_EQ(nullptr, LastDeallocatedPtr);
}
Ptr = NewPtr;
}
}
}

View file

@ -45,4 +45,10 @@ using Test = ::testing::Test;
#define SKIP_NO_DEBUG(T) DISABLED_##T #define SKIP_NO_DEBUG(T) DISABLED_##T
#endif #endif
#if SCUDO_FUCHSIA
// The zxtest library provides a default main function that does the same thing
// for Fuchsia builds.
#define SCUDO_NO_TEST_MAIN
#endif
extern bool UseQuarantine; extern bool UseQuarantine;

View file

@ -45,9 +45,7 @@ __scudo_default_options() {
"dealloc_type_mismatch=" DEALLOC_TYPE_MISMATCH; "dealloc_type_mismatch=" DEALLOC_TYPE_MISMATCH;
} }
// The zxtest library provides a default main function that does the same thing #if !defined(SCUDO_NO_TEST_MAIN)
// for Fuchsia builds.
#if !SCUDO_FUCHSIA
int main(int argc, char **argv) { int main(int argc, char **argv) {
EnableMemoryTaggingIfSupported(); EnableMemoryTaggingIfSupported();
testing::InitGoogleTest(&argc, argv); testing::InitGoogleTest(&argc, argv);

View file

@ -20,10 +20,6 @@ TEST(ScudoSizeClassMapTest, DefaultSizeClassMap) {
testSizeClassMap<scudo::DefaultSizeClassMap>(); testSizeClassMap<scudo::DefaultSizeClassMap>();
} }
TEST(ScudoSizeClassMapTest, SvelteSizeClassMap) {
testSizeClassMap<scudo::SvelteSizeClassMap>();
}
TEST(ScudoSizeClassMapTest, AndroidSizeClassMap) { TEST(ScudoSizeClassMapTest, AndroidSizeClassMap) {
testSizeClassMap<scudo::AndroidSizeClassMap>(); testSizeClassMap<scudo::AndroidSizeClassMap>();
} }

View file

@ -101,6 +101,7 @@ template <class AllocatorT> static void testRegistry() {
bool UnlockRequired; bool UnlockRequired;
auto TSD = Registry->getTSDAndLock(&UnlockRequired); auto TSD = Registry->getTSDAndLock(&UnlockRequired);
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
EXPECT_NE(TSD, nullptr); EXPECT_NE(TSD, nullptr);
EXPECT_EQ(TSD->getCache().Canary, 0U); EXPECT_EQ(TSD->getCache().Canary, 0U);
if (UnlockRequired) if (UnlockRequired)
@ -108,6 +109,7 @@ template <class AllocatorT> static void testRegistry() {
Registry->initThreadMaybe(Allocator.get(), /*MinimalInit=*/false); Registry->initThreadMaybe(Allocator.get(), /*MinimalInit=*/false);
TSD = Registry->getTSDAndLock(&UnlockRequired); TSD = Registry->getTSDAndLock(&UnlockRequired);
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
EXPECT_NE(TSD, nullptr); EXPECT_NE(TSD, nullptr);
EXPECT_EQ(TSD->getCache().Canary, 0U); EXPECT_EQ(TSD->getCache().Canary, 0U);
memset(&TSD->getCache(), 0x42, sizeof(TSD->getCache())); memset(&TSD->getCache(), 0x42, sizeof(TSD->getCache()));
@ -137,6 +139,7 @@ template <typename AllocatorT> static void stressCache(AllocatorT *Allocator) {
Registry->initThreadMaybe(Allocator, /*MinimalInit=*/false); Registry->initThreadMaybe(Allocator, /*MinimalInit=*/false);
bool UnlockRequired; bool UnlockRequired;
auto TSD = Registry->getTSDAndLock(&UnlockRequired); auto TSD = Registry->getTSDAndLock(&UnlockRequired);
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
EXPECT_NE(TSD, nullptr); EXPECT_NE(TSD, nullptr);
// For an exclusive TSD, the cache should be empty. We cannot guarantee the // For an exclusive TSD, the cache should be empty. We cannot guarantee the
// same for a shared TSD. // same for a shared TSD.
@ -195,6 +198,7 @@ static void stressSharedRegistry(MockAllocator<SharedCaches> *Allocator) {
bool UnlockRequired; bool UnlockRequired;
for (scudo::uptr I = 0; I < 4096U; I++) { for (scudo::uptr I = 0; I < 4096U; I++) {
auto TSD = Registry->getTSDAndLock(&UnlockRequired); auto TSD = Registry->getTSDAndLock(&UnlockRequired);
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
EXPECT_NE(TSD, nullptr); EXPECT_NE(TSD, nullptr);
Set.insert(reinterpret_cast<void *>(TSD)); Set.insert(reinterpret_cast<void *>(TSD));
if (UnlockRequired) if (UnlockRequired)

View file

@ -45,7 +45,101 @@ int malloc_iterate(uintptr_t base, size_t size,
void *arg); void *arg);
void *valloc(size_t size); void *valloc(size_t size);
void *pvalloc(size_t size); void *pvalloc(size_t size);
#ifndef SCUDO_ENABLE_HOOKS_TESTS
#define SCUDO_ENABLE_HOOKS_TESTS 0
#endif
#if (SCUDO_ENABLE_HOOKS_TESTS == 1) && (SCUDO_ENABLE_HOOKS == 0)
#error "Hooks tests should have hooks enabled as well!"
#endif
struct AllocContext {
void *Ptr;
size_t Size;
};
struct DeallocContext {
void *Ptr;
};
struct ReallocContext {
void *AllocPtr;
void *DeallocPtr;
size_t Size;
};
static AllocContext AC;
static DeallocContext DC;
static ReallocContext RC;
#if (SCUDO_ENABLE_HOOKS_TESTS == 1)
__attribute__((visibility("default"))) void __scudo_allocate_hook(void *Ptr,
size_t Size) {
AC.Ptr = Ptr;
AC.Size = Size;
} }
__attribute__((visibility("default"))) void __scudo_deallocate_hook(void *Ptr) {
DC.Ptr = Ptr;
}
__attribute__((visibility("default"))) void
__scudo_realloc_allocate_hook(void *OldPtr, void *NewPtr, size_t Size) {
// Verify that __scudo_realloc_deallocate_hook is called first and set the
// right pointer.
EXPECT_EQ(OldPtr, RC.DeallocPtr);
RC.AllocPtr = NewPtr;
RC.Size = Size;
// Note that this is only used for testing. In general, only one pair of hooks
// will be invoked in `realloc`. if __scudo_realloc_*_hook are not defined,
// it'll call the general hooks only. To make the test easier, we call the
// general one here so that either case (whether __scudo_realloc_*_hook are
// defined) will be verified without separating them into different tests.
__scudo_allocate_hook(NewPtr, Size);
}
__attribute__((visibility("default"))) void
__scudo_realloc_deallocate_hook(void *Ptr) {
RC.DeallocPtr = Ptr;
// See the comment in the __scudo_realloc_allocate_hook above.
__scudo_deallocate_hook(Ptr);
}
#endif // (SCUDO_ENABLE_HOOKS_TESTS == 1)
}
class ScudoWrappersCTest : public Test {
protected:
void SetUp() override {
if (SCUDO_ENABLE_HOOKS && !SCUDO_ENABLE_HOOKS_TESTS)
printf("Hooks are enabled but hooks tests are disabled.\n");
}
void invalidateHookPtrs() {
if (SCUDO_ENABLE_HOOKS_TESTS) {
void *InvalidPtr = reinterpret_cast<void *>(0xdeadbeef);
AC.Ptr = InvalidPtr;
DC.Ptr = InvalidPtr;
RC.AllocPtr = RC.DeallocPtr = InvalidPtr;
}
}
void verifyAllocHookPtr(UNUSED void *Ptr) {
if (SCUDO_ENABLE_HOOKS_TESTS)
EXPECT_EQ(Ptr, AC.Ptr);
}
void verifyAllocHookSize(UNUSED size_t Size) {
if (SCUDO_ENABLE_HOOKS_TESTS)
EXPECT_EQ(Size, AC.Size);
}
void verifyDeallocHookPtr(UNUSED void *Ptr) {
if (SCUDO_ENABLE_HOOKS_TESTS)
EXPECT_EQ(Ptr, DC.Ptr);
}
void verifyReallocHookPtrs(UNUSED void *OldPtr, void *NewPtr, size_t Size) {
if (SCUDO_ENABLE_HOOKS_TESTS) {
EXPECT_EQ(OldPtr, RC.DeallocPtr);
EXPECT_EQ(NewPtr, RC.AllocPtr);
EXPECT_EQ(Size, RC.Size);
}
}
};
using ScudoWrappersCDeathTest = ScudoWrappersCTest;
// Note that every C allocation function in the test binary will be fulfilled // Note that every C allocation function in the test binary will be fulfilled
// by Scudo (this includes the gtest APIs, etc.), which is a test by itself. // by Scudo (this includes the gtest APIs, etc.), which is a test by itself.
@ -59,11 +153,13 @@ void *pvalloc(size_t size);
static const size_t Size = 100U; static const size_t Size = 100U;
TEST(ScudoWrappersCDeathTest, Malloc) { TEST_F(ScudoWrappersCDeathTest, Malloc) {
void *P = malloc(Size); void *P = malloc(Size);
EXPECT_NE(P, nullptr); EXPECT_NE(P, nullptr);
EXPECT_LE(Size, malloc_usable_size(P)); EXPECT_LE(Size, malloc_usable_size(P));
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % FIRST_32_SECOND_64(8U, 16U), 0U); EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % FIRST_32_SECOND_64(8U, 16U), 0U);
verifyAllocHookPtr(P);
verifyAllocHookSize(Size);
// An update to this warning in Clang now triggers in this line, but it's ok // An update to this warning in Clang now triggers in this line, but it's ok
// because the check is expecting a bad pointer and should fail. // because the check is expecting a bad pointer and should fail.
@ -78,6 +174,7 @@ TEST(ScudoWrappersCDeathTest, Malloc) {
#endif #endif
free(P); free(P);
verifyDeallocHookPtr(P);
EXPECT_DEATH(free(P), ""); EXPECT_DEATH(free(P), "");
P = malloc(0U); P = malloc(0U);
@ -89,13 +186,16 @@ TEST(ScudoWrappersCDeathTest, Malloc) {
EXPECT_EQ(errno, ENOMEM); EXPECT_EQ(errno, ENOMEM);
} }
TEST(ScudoWrappersCTest, Calloc) { TEST_F(ScudoWrappersCTest, Calloc) {
void *P = calloc(1U, Size); void *P = calloc(1U, Size);
EXPECT_NE(P, nullptr); EXPECT_NE(P, nullptr);
EXPECT_LE(Size, malloc_usable_size(P)); EXPECT_LE(Size, malloc_usable_size(P));
verifyAllocHookPtr(P);
verifyAllocHookSize(Size);
for (size_t I = 0; I < Size; I++) for (size_t I = 0; I < Size; I++)
EXPECT_EQ((reinterpret_cast<uint8_t *>(P))[I], 0U); EXPECT_EQ((reinterpret_cast<uint8_t *>(P))[I], 0U);
free(P); free(P);
verifyDeallocHookPtr(P);
P = calloc(1U, 0U); P = calloc(1U, 0U);
EXPECT_NE(P, nullptr); EXPECT_NE(P, nullptr);
@ -116,7 +216,7 @@ TEST(ScudoWrappersCTest, Calloc) {
EXPECT_EQ(errno, ENOMEM); EXPECT_EQ(errno, ENOMEM);
} }
TEST(ScudoWrappersCTest, SmallAlign) { TEST_F(ScudoWrappersCTest, SmallAlign) {
// Allocating pointers by the powers of 2 from 1 to 0x10000 // Allocating pointers by the powers of 2 from 1 to 0x10000
// Using powers of 2 due to memalign using powers of 2 and test more sizes // Using powers of 2 due to memalign using powers of 2 and test more sizes
constexpr size_t MaxSize = 0x10000; constexpr size_t MaxSize = 0x10000;
@ -137,7 +237,7 @@ TEST(ScudoWrappersCTest, SmallAlign) {
free(ptr); free(ptr);
} }
TEST(ScudoWrappersCTest, Memalign) { TEST_F(ScudoWrappersCTest, Memalign) {
void *P; void *P;
for (size_t I = FIRST_32_SECOND_64(2U, 3U); I <= 18U; I++) { for (size_t I = FIRST_32_SECOND_64(2U, 3U); I <= 18U; I++) {
const size_t Alignment = 1U << I; const size_t Alignment = 1U << I;
@ -146,14 +246,20 @@ TEST(ScudoWrappersCTest, Memalign) {
EXPECT_NE(P, nullptr); EXPECT_NE(P, nullptr);
EXPECT_LE(Size, malloc_usable_size(P)); EXPECT_LE(Size, malloc_usable_size(P));
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U); EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
verifyAllocHookPtr(P);
verifyAllocHookSize(Size);
free(P); free(P);
verifyDeallocHookPtr(P);
P = nullptr; P = nullptr;
EXPECT_EQ(posix_memalign(&P, Alignment, Size), 0); EXPECT_EQ(posix_memalign(&P, Alignment, Size), 0);
EXPECT_NE(P, nullptr); EXPECT_NE(P, nullptr);
EXPECT_LE(Size, malloc_usable_size(P)); EXPECT_LE(Size, malloc_usable_size(P));
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U); EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
verifyAllocHookPtr(P);
verifyAllocHookSize(Size);
free(P); free(P);
verifyDeallocHookPtr(P);
} }
EXPECT_EQ(memalign(4096U, SIZE_MAX), nullptr); EXPECT_EQ(memalign(4096U, SIZE_MAX), nullptr);
@ -165,18 +271,24 @@ TEST(ScudoWrappersCTest, Memalign) {
for (size_t Alignment = 0U; Alignment <= 128U; Alignment++) { for (size_t Alignment = 0U; Alignment <= 128U; Alignment++) {
P = memalign(Alignment, 1024U); P = memalign(Alignment, 1024U);
EXPECT_NE(P, nullptr); EXPECT_NE(P, nullptr);
verifyAllocHookPtr(P);
verifyAllocHookSize(Size);
free(P); free(P);
verifyDeallocHookPtr(P);
} }
} }
} }
TEST(ScudoWrappersCTest, AlignedAlloc) { TEST_F(ScudoWrappersCTest, AlignedAlloc) {
const size_t Alignment = 4096U; const size_t Alignment = 4096U;
void *P = aligned_alloc(Alignment, Alignment * 4U); void *P = aligned_alloc(Alignment, Alignment * 4U);
EXPECT_NE(P, nullptr); EXPECT_NE(P, nullptr);
EXPECT_LE(Alignment * 4U, malloc_usable_size(P)); EXPECT_LE(Alignment * 4U, malloc_usable_size(P));
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U); EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
verifyAllocHookPtr(P);
verifyAllocHookSize(Alignment * 4U);
free(P); free(P);
verifyDeallocHookPtr(P);
errno = 0; errno = 0;
P = aligned_alloc(Alignment, Size); P = aligned_alloc(Alignment, Size);
@ -184,33 +296,60 @@ TEST(ScudoWrappersCTest, AlignedAlloc) {
EXPECT_EQ(errno, EINVAL); EXPECT_EQ(errno, EINVAL);
} }
TEST(ScudoWrappersCDeathTest, Realloc) { TEST_F(ScudoWrappersCDeathTest, Realloc) {
invalidateHookPtrs();
// realloc(nullptr, N) is malloc(N) // realloc(nullptr, N) is malloc(N)
void *P = realloc(nullptr, 0U); void *P = realloc(nullptr, Size);
EXPECT_NE(P, nullptr); EXPECT_NE(P, nullptr);
verifyAllocHookPtr(P);
verifyAllocHookSize(Size);
free(P); free(P);
verifyDeallocHookPtr(P);
invalidateHookPtrs();
P = malloc(Size); P = malloc(Size);
EXPECT_NE(P, nullptr); EXPECT_NE(P, nullptr);
// realloc(P, 0U) is free(P) and returns nullptr // realloc(P, 0U) is free(P) and returns nullptr
EXPECT_EQ(realloc(P, 0U), nullptr); EXPECT_EQ(realloc(P, 0U), nullptr);
verifyDeallocHookPtr(P);
P = malloc(Size); P = malloc(Size);
EXPECT_NE(P, nullptr); EXPECT_NE(P, nullptr);
EXPECT_LE(Size, malloc_usable_size(P)); EXPECT_LE(Size, malloc_usable_size(P));
memset(P, 0x42, Size); memset(P, 0x42, Size);
invalidateHookPtrs();
void *OldP = P;
P = realloc(P, Size * 2U); P = realloc(P, Size * 2U);
EXPECT_NE(P, nullptr); EXPECT_NE(P, nullptr);
EXPECT_LE(Size * 2U, malloc_usable_size(P)); EXPECT_LE(Size * 2U, malloc_usable_size(P));
for (size_t I = 0; I < Size; I++) for (size_t I = 0; I < Size; I++)
EXPECT_EQ(0x42, (reinterpret_cast<uint8_t *>(P))[I]); EXPECT_EQ(0x42, (reinterpret_cast<uint8_t *>(P))[I]);
if (OldP == P) {
verifyDeallocHookPtr(OldP);
verifyAllocHookPtr(OldP);
} else {
verifyAllocHookPtr(P);
verifyAllocHookSize(Size * 2U);
verifyDeallocHookPtr(OldP);
}
verifyReallocHookPtrs(OldP, P, Size * 2U);
invalidateHookPtrs();
OldP = P;
P = realloc(P, Size / 2U); P = realloc(P, Size / 2U);
EXPECT_NE(P, nullptr); EXPECT_NE(P, nullptr);
EXPECT_LE(Size / 2U, malloc_usable_size(P)); EXPECT_LE(Size / 2U, malloc_usable_size(P));
for (size_t I = 0; I < Size / 2U; I++) for (size_t I = 0; I < Size / 2U; I++)
EXPECT_EQ(0x42, (reinterpret_cast<uint8_t *>(P))[I]); EXPECT_EQ(0x42, (reinterpret_cast<uint8_t *>(P))[I]);
if (OldP == P) {
verifyDeallocHookPtr(OldP);
verifyAllocHookPtr(OldP);
} else {
verifyAllocHookPtr(P);
verifyAllocHookSize(Size / 2U);
}
verifyReallocHookPtrs(OldP, P, Size / 2U);
free(P); free(P);
EXPECT_DEATH(P = realloc(P, Size), ""); EXPECT_DEATH(P = realloc(P, Size), "");
@ -244,7 +383,7 @@ TEST(ScudoWrappersCDeathTest, Realloc) {
} }
#if !SCUDO_FUCHSIA #if !SCUDO_FUCHSIA
TEST(ScudoWrappersCTest, MallOpt) { TEST_F(ScudoWrappersCTest, MallOpt) {
errno = 0; errno = 0;
EXPECT_EQ(mallopt(-1000, 1), 0); EXPECT_EQ(mallopt(-1000, 1), 0);
// mallopt doesn't set errno. // mallopt doesn't set errno.
@ -265,7 +404,7 @@ TEST(ScudoWrappersCTest, MallOpt) {
} }
#endif #endif
TEST(ScudoWrappersCTest, OtherAlloc) { TEST_F(ScudoWrappersCTest, OtherAlloc) {
#if HAVE_PVALLOC #if HAVE_PVALLOC
const size_t PageSize = static_cast<size_t>(sysconf(_SC_PAGESIZE)); const size_t PageSize = static_cast<size_t>(sysconf(_SC_PAGESIZE));
@ -273,7 +412,11 @@ TEST(ScudoWrappersCTest, OtherAlloc) {
EXPECT_NE(P, nullptr); EXPECT_NE(P, nullptr);
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) & (PageSize - 1), 0U); EXPECT_EQ(reinterpret_cast<uintptr_t>(P) & (PageSize - 1), 0U);
EXPECT_LE(PageSize, malloc_usable_size(P)); EXPECT_LE(PageSize, malloc_usable_size(P));
verifyAllocHookPtr(P);
// Size will be rounded up to PageSize.
verifyAllocHookSize(PageSize);
free(P); free(P);
verifyDeallocHookPtr(P);
EXPECT_EQ(pvalloc(SIZE_MAX), nullptr); EXPECT_EQ(pvalloc(SIZE_MAX), nullptr);
@ -288,29 +431,39 @@ TEST(ScudoWrappersCTest, OtherAlloc) {
#endif #endif
} }
#if !SCUDO_FUCHSIA template<typename FieldType>
TEST(ScudoWrappersCTest, MallInfo) { void MallInfoTest() {
// mallinfo is deprecated. // mallinfo is deprecated.
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations" #pragma clang diagnostic ignored "-Wdeprecated-declarations"
const size_t BypassQuarantineSize = 1024U; const FieldType BypassQuarantineSize = 1024U;
struct mallinfo MI = mallinfo(); struct mallinfo MI = mallinfo();
size_t Allocated = MI.uordblks; FieldType Allocated = MI.uordblks;
void *P = malloc(BypassQuarantineSize); void *P = malloc(BypassQuarantineSize);
EXPECT_NE(P, nullptr); EXPECT_NE(P, nullptr);
MI = mallinfo(); MI = mallinfo();
EXPECT_GE(static_cast<size_t>(MI.uordblks), Allocated + BypassQuarantineSize); EXPECT_GE(MI.uordblks, Allocated + BypassQuarantineSize);
EXPECT_GT(static_cast<size_t>(MI.hblkhd), 0U); EXPECT_GT(MI.hblkhd, static_cast<FieldType>(0));
size_t Free = MI.fordblks; FieldType Free = MI.fordblks;
free(P); free(P);
MI = mallinfo(); MI = mallinfo();
EXPECT_GE(static_cast<size_t>(MI.fordblks), Free + BypassQuarantineSize); EXPECT_GE(MI.fordblks, Free + BypassQuarantineSize);
#pragma clang diagnostic pop #pragma clang diagnostic pop
} }
#if !SCUDO_FUCHSIA
TEST_F(ScudoWrappersCTest, MallInfo) {
#if SCUDO_ANDROID
// Android accidentally set the fields to size_t instead of int.
MallInfoTest<size_t>();
#else
MallInfoTest<int>();
#endif
}
#endif #endif
#if __GLIBC_PREREQ(2, 33) #if __GLIBC_PREREQ(2, 33) || SCUDO_ANDROID
TEST(ScudoWrappersCTest, MallInfo2) { TEST_F(ScudoWrappersCTest, MallInfo2) {
const size_t BypassQuarantineSize = 1024U; const size_t BypassQuarantineSize = 1024U;
struct mallinfo2 MI = mallinfo2(); struct mallinfo2 MI = mallinfo2();
size_t Allocated = MI.uordblks; size_t Allocated = MI.uordblks;
@ -342,7 +495,7 @@ static void callback(uintptr_t Base, UNUSED size_t Size, UNUSED void *Arg) {
// To achieve this, we allocate a chunk for which the backing block will be // To achieve this, we allocate a chunk for which the backing block will be
// aligned on a page, then run the malloc_iterate on both the pages that the // aligned on a page, then run the malloc_iterate on both the pages that the
// block is a boundary for. It must only be seen once by the callback function. // block is a boundary for. It must only be seen once by the callback function.
TEST(ScudoWrappersCTest, MallocIterateBoundary) { TEST_F(ScudoWrappersCTest, MallocIterateBoundary) {
const size_t PageSize = static_cast<size_t>(sysconf(_SC_PAGESIZE)); const size_t PageSize = static_cast<size_t>(sysconf(_SC_PAGESIZE));
#if SCUDO_ANDROID #if SCUDO_ANDROID
// Android uses a 16 byte alignment for both 32 bit and 64 bit. // Android uses a 16 byte alignment for both 32 bit and 64 bit.
@ -394,7 +547,7 @@ TEST(ScudoWrappersCTest, MallocIterateBoundary) {
// Fuchsia doesn't have alarm, fork or malloc_info. // Fuchsia doesn't have alarm, fork or malloc_info.
#if !SCUDO_FUCHSIA #if !SCUDO_FUCHSIA
TEST(ScudoWrappersCDeathTest, MallocDisableDeadlock) { TEST_F(ScudoWrappersCDeathTest, MallocDisableDeadlock) {
// We expect heap operations within a disable/enable scope to deadlock. // We expect heap operations within a disable/enable scope to deadlock.
EXPECT_DEATH( EXPECT_DEATH(
{ {
@ -409,7 +562,7 @@ TEST(ScudoWrappersCDeathTest, MallocDisableDeadlock) {
""); "");
} }
TEST(ScudoWrappersCTest, MallocInfo) { TEST_F(ScudoWrappersCTest, MallocInfo) {
// Use volatile so that the allocations don't get optimized away. // Use volatile so that the allocations don't get optimized away.
void *volatile P1 = malloc(1234); void *volatile P1 = malloc(1234);
void *volatile P2 = malloc(4321); void *volatile P2 = malloc(4321);
@ -429,7 +582,7 @@ TEST(ScudoWrappersCTest, MallocInfo) {
free(P2); free(P2);
} }
TEST(ScudoWrappersCDeathTest, Fork) { TEST_F(ScudoWrappersCDeathTest, Fork) {
void *P; void *P;
pid_t Pid = fork(); pid_t Pid = fork();
EXPECT_GE(Pid, 0) << strerror(errno); EXPECT_GE(Pid, 0) << strerror(errno);
@ -481,7 +634,7 @@ static void *enableMalloc(UNUSED void *Unused) {
return nullptr; return nullptr;
} }
TEST(ScudoWrappersCTest, DisableForkEnable) { TEST_F(ScudoWrappersCTest, DisableForkEnable) {
pthread_t ThreadId; pthread_t ThreadId;
Ready = false; Ready = false;
EXPECT_EQ(pthread_create(&ThreadId, nullptr, &enableMalloc, nullptr), 0); EXPECT_EQ(pthread_create(&ThreadId, nullptr, &enableMalloc, nullptr), 0);

View file

@ -27,45 +27,109 @@
void operator delete(void *, size_t) noexcept; void operator delete(void *, size_t) noexcept;
void operator delete[](void *, size_t) noexcept; void operator delete[](void *, size_t) noexcept;
// Note that every Cxx allocation function in the test binary will be fulfilled extern "C" {
// by Scudo. See the comment in the C counterpart of this file. #ifndef SCUDO_ENABLE_HOOKS_TESTS
#define SCUDO_ENABLE_HOOKS_TESTS 0
#endif
template <typename T> static void testCxxNew() { #if (SCUDO_ENABLE_HOOKS_TESTS == 1) && (SCUDO_ENABLE_HOOKS == 0)
T *P = new T; #error "Hooks tests should have hooks enabled as well!"
EXPECT_NE(P, nullptr); #endif
memset(P, 0x42, sizeof(T));
EXPECT_DEATH(delete[] P, "");
delete P;
EXPECT_DEATH(delete P, "");
P = new T; struct AllocContext {
EXPECT_NE(P, nullptr); void *Ptr;
memset(P, 0x42, sizeof(T)); size_t Size;
operator delete(P, sizeof(T)); };
struct DeallocContext {
void *Ptr;
};
static AllocContext AC;
static DeallocContext DC;
P = new (std::nothrow) T; #if (SCUDO_ENABLE_HOOKS_TESTS == 1)
EXPECT_NE(P, nullptr); __attribute__((visibility("default"))) void __scudo_allocate_hook(void *Ptr,
memset(P, 0x42, sizeof(T)); size_t Size) {
delete P; AC.Ptr = Ptr;
AC.Size = Size;
const size_t N = 16U;
T *A = new T[N];
EXPECT_NE(A, nullptr);
memset(A, 0x42, sizeof(T) * N);
EXPECT_DEATH(delete A, "");
delete[] A;
EXPECT_DEATH(delete[] A, "");
A = new T[N];
EXPECT_NE(A, nullptr);
memset(A, 0x42, sizeof(T) * N);
operator delete[](A, sizeof(T) * N);
A = new (std::nothrow) T[N];
EXPECT_NE(A, nullptr);
memset(A, 0x42, sizeof(T) * N);
delete[] A;
} }
__attribute__((visibility("default"))) void __scudo_deallocate_hook(void *Ptr) {
DC.Ptr = Ptr;
}
#endif // (SCUDO_ENABLE_HOOKS_TESTS == 1)
}
class ScudoWrappersCppTest : public Test {
protected:
void SetUp() override {
if (SCUDO_ENABLE_HOOKS && !SCUDO_ENABLE_HOOKS_TESTS)
printf("Hooks are enabled but hooks tests are disabled.\n");
}
void verifyAllocHookPtr(UNUSED void *Ptr) {
if (SCUDO_ENABLE_HOOKS_TESTS)
EXPECT_EQ(Ptr, AC.Ptr);
}
void verifyAllocHookSize(UNUSED size_t Size) {
if (SCUDO_ENABLE_HOOKS_TESTS)
EXPECT_EQ(Size, AC.Size);
}
void verifyDeallocHookPtr(UNUSED void *Ptr) {
if (SCUDO_ENABLE_HOOKS_TESTS)
EXPECT_EQ(Ptr, DC.Ptr);
}
template <typename T> void testCxxNew() {
T *P = new T;
EXPECT_NE(P, nullptr);
verifyAllocHookPtr(P);
verifyAllocHookSize(sizeof(T));
memset(P, 0x42, sizeof(T));
EXPECT_DEATH(delete[] P, "");
delete P;
verifyDeallocHookPtr(P);
EXPECT_DEATH(delete P, "");
P = new T;
EXPECT_NE(P, nullptr);
memset(P, 0x42, sizeof(T));
operator delete(P, sizeof(T));
verifyDeallocHookPtr(P);
P = new (std::nothrow) T;
verifyAllocHookPtr(P);
verifyAllocHookSize(sizeof(T));
EXPECT_NE(P, nullptr);
memset(P, 0x42, sizeof(T));
delete P;
verifyDeallocHookPtr(P);
const size_t N = 16U;
T *A = new T[N];
EXPECT_NE(A, nullptr);
verifyAllocHookPtr(A);
verifyAllocHookSize(sizeof(T) * N);
memset(A, 0x42, sizeof(T) * N);
EXPECT_DEATH(delete A, "");
delete[] A;
verifyDeallocHookPtr(A);
EXPECT_DEATH(delete[] A, "");
A = new T[N];
EXPECT_NE(A, nullptr);
memset(A, 0x42, sizeof(T) * N);
operator delete[](A, sizeof(T) * N);
verifyDeallocHookPtr(A);
A = new (std::nothrow) T[N];
verifyAllocHookPtr(A);
verifyAllocHookSize(sizeof(T) * N);
EXPECT_NE(A, nullptr);
memset(A, 0x42, sizeof(T) * N);
delete[] A;
verifyDeallocHookPtr(A);
}
};
using ScudoWrappersCppDeathTest = ScudoWrappersCppTest;
class Pixel { class Pixel {
public: public:
@ -75,7 +139,10 @@ public:
Color C = Color::Red; Color C = Color::Red;
}; };
TEST(ScudoWrappersCppDeathTest, New) { // Note that every Cxx allocation function in the test binary will be fulfilled
// by Scudo. See the comment in the C counterpart of this file.
TEST_F(ScudoWrappersCppDeathTest, New) {
if (getenv("SKIP_TYPE_MISMATCH") || SKIP_MISMATCH_TESTS) { if (getenv("SKIP_TYPE_MISMATCH") || SKIP_MISMATCH_TESTS) {
printf("Skipped type mismatch tests.\n"); printf("Skipped type mismatch tests.\n");
return; return;
@ -116,7 +183,7 @@ static void stressNew() {
} }
} }
TEST(ScudoWrappersCppTest, ThreadedNew) { TEST_F(ScudoWrappersCppTest, ThreadedNew) {
// TODO: Investigate why libc sometimes crashes with tag missmatch in // TODO: Investigate why libc sometimes crashes with tag missmatch in
// __pthread_clockjoin_ex. // __pthread_clockjoin_ex.
std::unique_ptr<scudo::ScopedDisableMemoryTagChecks> NoTags; std::unique_ptr<scudo::ScopedDisableMemoryTagChecks> NoTags;
@ -138,7 +205,7 @@ TEST(ScudoWrappersCppTest, ThreadedNew) {
} }
#if !SCUDO_FUCHSIA #if !SCUDO_FUCHSIA
TEST(ScudoWrappersCppTest, AllocAfterFork) { TEST_F(ScudoWrappersCppTest, AllocAfterFork) {
// This test can fail flakily when ran as a part of large number of // This test can fail flakily when ran as a part of large number of
// other tests if the maxmimum number of mappings allowed is low. // other tests if the maxmimum number of mappings allowed is low.
// We tried to reduce the number of iterations of the loops with // We tried to reduce the number of iterations of the loops with

View file

@ -12,6 +12,7 @@
#include "common.h" #include "common.h"
#include "mutex.h" #include "mutex.h"
#include "report_linux.h"
#include "trusty.h" #include "trusty.h"
#include <errno.h> // for errno #include <errno.h> // for errno
@ -50,7 +51,8 @@ void *map(void *Addr, uptr Size, const char *Name, uptr Flags,
if (IS_ERR(P)) { if (IS_ERR(P)) {
errno = lk_err_to_errno(PTR_ERR(P)); errno = lk_err_to_errno(PTR_ERR(P));
dieOnMapUnmapError(Size); if (!(Flags & MAP_ALLOWNOMEM) || errno != ENOMEM)
reportMapError(Size);
return nullptr; return nullptr;
} }
@ -60,7 +62,7 @@ void *map(void *Addr, uptr Size, const char *Name, uptr Flags,
void unmap(UNUSED void *Addr, UNUSED uptr Size, UNUSED uptr Flags, void unmap(UNUSED void *Addr, UNUSED uptr Size, UNUSED uptr Flags,
UNUSED MapPlatformData *Data) { UNUSED MapPlatformData *Data) {
if (_trusty_munmap(Addr, Size) != 0) if (_trusty_munmap(Addr, Size) != 0)
dieOnMapUnmapError(); reportUnmapError(reinterpret_cast<uptr>(Addr), Size);
} }
void setMemoryPermission(UNUSED uptr Addr, UNUSED uptr Size, UNUSED uptr Flags, void setMemoryPermission(UNUSED uptr Addr, UNUSED uptr Size, UNUSED uptr Flags,

View file

@ -53,8 +53,14 @@ template <class Allocator> struct alignas(SCUDO_CACHE_LINE_SIZE) TSD {
inline void unlock() NO_THREAD_SAFETY_ANALYSIS { Mutex.unlock(); } inline void unlock() NO_THREAD_SAFETY_ANALYSIS { Mutex.unlock(); }
inline uptr getPrecedence() { return atomic_load_relaxed(&Precedence); } inline uptr getPrecedence() { return atomic_load_relaxed(&Precedence); }
void commitBack(Allocator *Instance) ASSERT_CAPABILITY(Mutex) { void commitBack(Allocator *Instance) { Instance->commitBack(this); }
Instance->commitBack(this);
// As the comments attached to `getCache()`, the TSD doesn't always need to be
// locked. In that case, we would only skip the check before we have all TSDs
// locked in all paths.
void assertLocked(bool BypassCheck) ASSERT_CAPABILITY(Mutex) {
if (SCUDO_DEBUG && !BypassCheck)
Mutex.assertHeld();
} }
// Ideally, we may want to assert that all the operations on // Ideally, we may want to assert that all the operations on
@ -66,11 +72,8 @@ template <class Allocator> struct alignas(SCUDO_CACHE_LINE_SIZE) TSD {
// TODO(chiahungduan): Ideally, we want to do `Mutex.assertHeld` but acquiring // TODO(chiahungduan): Ideally, we want to do `Mutex.assertHeld` but acquiring
// TSD doesn't always require holding the lock. Add this assertion while the // TSD doesn't always require holding the lock. Add this assertion while the
// lock is always acquired. // lock is always acquired.
typename Allocator::CacheT &getCache() ASSERT_CAPABILITY(Mutex) { typename Allocator::CacheT &getCache() REQUIRES(Mutex) { return Cache; }
return Cache; typename Allocator::QuarantineCacheT &getQuarantineCache() REQUIRES(Mutex) {
}
typename Allocator::QuarantineCacheT &getQuarantineCache()
ASSERT_CAPABILITY(Mutex) {
return QuarantineCache; return QuarantineCache;
} }

View file

@ -120,6 +120,11 @@ struct TSDRegistrySharedT {
TSDsArraySize); TSDsArraySize);
for (uptr I = 0; I < NumberOfTSDs; ++I) { for (uptr I = 0; I < NumberOfTSDs; ++I) {
TSDs[I].lock(); TSDs[I].lock();
// Theoretically, we want to mark TSD::lock()/TSD::unlock() with proper
// thread annotations. However, given the TSD is only locked on shared
// path, do the assertion in a separate path to avoid confusing the
// analyzer.
TSDs[I].assertLocked(/*BypassCheck=*/true);
Str->append(" Shared TSD[%zu]:\n", I); Str->append(" Shared TSD[%zu]:\n", I);
TSDs[I].getCache().getStats(Str); TSDs[I].getCache().getStats(Str);
TSDs[I].unlock(); TSDs[I].unlock();

View file

@ -9,26 +9,20 @@
#ifndef SCUDO_VECTOR_H_ #ifndef SCUDO_VECTOR_H_
#define SCUDO_VECTOR_H_ #define SCUDO_VECTOR_H_
#include "common.h" #include "mem_map.h"
#include <string.h> #include <string.h>
namespace scudo { namespace scudo {
// A low-level vector based on map. May incur a significant memory overhead for // A low-level vector based on map. It stores the contents inline up to a fixed
// small vectors. The current implementation supports only POD types. // capacity, or in an external memory buffer if it grows bigger than that. May
// incur a significant memory overhead for small vectors. The current
// implementation supports only POD types.
//
// NOTE: This class is not meant to be used directly, use Vector<T> instead.
template <typename T> class VectorNoCtor { template <typename T> class VectorNoCtor {
public: public:
constexpr void init(uptr InitialCapacity = 0) {
Data = &LocalData[0];
CapacityBytes = sizeof(LocalData);
if (InitialCapacity > capacity())
reserve(InitialCapacity);
}
void destroy() {
if (Data != &LocalData[0])
unmap(Data, CapacityBytes, 0, &MapData);
}
T &operator[](uptr I) { T &operator[](uptr I) {
DCHECK_LT(I, Size); DCHECK_LT(I, Size);
return Data[I]; return Data[I];
@ -78,24 +72,43 @@ public:
const T *end() const { return data() + size(); } const T *end() const { return data() + size(); }
T *end() { return data() + size(); } T *end() { return data() + size(); }
protected:
constexpr void init(uptr InitialCapacity = 0) {
Data = &LocalData[0];
CapacityBytes = sizeof(LocalData);
if (InitialCapacity > capacity())
reserve(InitialCapacity);
}
void destroy() {
if (Data != &LocalData[0])
ExternalBuffer.unmap(ExternalBuffer.getBase(),
ExternalBuffer.getCapacity());
}
private: private:
void reallocate(uptr NewCapacity) { void reallocate(uptr NewCapacity) {
DCHECK_GT(NewCapacity, 0); DCHECK_GT(NewCapacity, 0);
DCHECK_LE(Size, NewCapacity); DCHECK_LE(Size, NewCapacity);
MemMapT NewExternalBuffer;
NewCapacity = roundUp(NewCapacity * sizeof(T), getPageSizeCached()); NewCapacity = roundUp(NewCapacity * sizeof(T), getPageSizeCached());
T *NewData = reinterpret_cast<T *>( NewExternalBuffer.map(/*Addr=*/0U, NewCapacity, "scudo:vector");
map(nullptr, NewCapacity, "scudo:vector", 0, &MapData)); T *NewExternalData = reinterpret_cast<T *>(NewExternalBuffer.getBase());
memcpy(NewData, Data, Size * sizeof(T));
memcpy(NewExternalData, Data, Size * sizeof(T));
destroy(); destroy();
Data = NewData;
Data = NewExternalData;
CapacityBytes = NewCapacity; CapacityBytes = NewCapacity;
ExternalBuffer = NewExternalBuffer;
} }
T *Data = nullptr; T *Data = nullptr;
T LocalData[256 / sizeof(T)] = {};
uptr CapacityBytes = 0; uptr CapacityBytes = 0;
uptr Size = 0; uptr Size = 0;
[[no_unique_address]] MapPlatformData MapData = {};
T LocalData[256 / sizeof(T)] = {};
MemMapT ExternalBuffer;
}; };
template <typename T> class Vector : public VectorNoCtor<T> { template <typename T> class Vector : public VectorNoCtor<T> {

View file

@ -12,6 +12,9 @@
#if !SCUDO_ANDROID || !_BIONIC #if !SCUDO_ANDROID || !_BIONIC
#include "allocator_config.h" #include "allocator_config.h"
#include "internal_defs.h"
#include "platform.h"
#include "scudo/interface.h"
#include "wrappers_c.h" #include "wrappers_c.h"
#include "wrappers_c_checks.h" #include "wrappers_c_checks.h"

View file

@ -17,6 +17,35 @@
#define SCUDO_MALLOC_ALIGNMENT FIRST_32_SECOND_64(8U, 16U) #define SCUDO_MALLOC_ALIGNMENT FIRST_32_SECOND_64(8U, 16U)
#endif #endif
static void reportAllocation(void *ptr, size_t size) {
if (SCUDO_ENABLE_HOOKS)
if (__scudo_allocate_hook && ptr)
__scudo_allocate_hook(ptr, size);
}
static void reportDeallocation(void *ptr) {
if (SCUDO_ENABLE_HOOKS)
if (__scudo_deallocate_hook)
__scudo_deallocate_hook(ptr);
}
static void reportReallocAllocation(void *old_ptr, void *new_ptr, size_t size) {
DCHECK_NE(new_ptr, nullptr);
if (SCUDO_ENABLE_HOOKS) {
if (__scudo_realloc_allocate_hook)
__scudo_realloc_allocate_hook(old_ptr, new_ptr, size);
else if (__scudo_allocate_hook)
__scudo_allocate_hook(new_ptr, size);
}
}
static void reportReallocDeallocation(void *old_ptr) {
if (SCUDO_ENABLE_HOOKS) {
if (__scudo_realloc_deallocate_hook)
__scudo_realloc_deallocate_hook(old_ptr);
else if (__scudo_deallocate_hook)
__scudo_deallocate_hook(old_ptr);
}
}
extern "C" { extern "C" {
INTERFACE WEAK void *SCUDO_PREFIX(calloc)(size_t nmemb, size_t size) { INTERFACE WEAK void *SCUDO_PREFIX(calloc)(size_t nmemb, size_t size) {
@ -28,11 +57,14 @@ INTERFACE WEAK void *SCUDO_PREFIX(calloc)(size_t nmemb, size_t size) {
} }
scudo::reportCallocOverflow(nmemb, size); scudo::reportCallocOverflow(nmemb, size);
} }
return scudo::setErrnoOnNull(SCUDO_ALLOCATOR.allocate( void *Ptr = SCUDO_ALLOCATOR.allocate(Product, scudo::Chunk::Origin::Malloc,
Product, scudo::Chunk::Origin::Malloc, SCUDO_MALLOC_ALIGNMENT, true)); SCUDO_MALLOC_ALIGNMENT, true);
reportAllocation(Ptr, Product);
return scudo::setErrnoOnNull(Ptr);
} }
INTERFACE WEAK void SCUDO_PREFIX(free)(void *ptr) { INTERFACE WEAK void SCUDO_PREFIX(free)(void *ptr) {
reportDeallocation(ptr);
SCUDO_ALLOCATOR.deallocate(ptr, scudo::Chunk::Origin::Malloc); SCUDO_ALLOCATOR.deallocate(ptr, scudo::Chunk::Origin::Malloc);
} }
@ -75,8 +107,10 @@ INTERFACE WEAK struct __scudo_mallinfo2 SCUDO_PREFIX(mallinfo2)(void) {
#endif #endif
INTERFACE WEAK void *SCUDO_PREFIX(malloc)(size_t size) { INTERFACE WEAK void *SCUDO_PREFIX(malloc)(size_t size) {
return scudo::setErrnoOnNull(SCUDO_ALLOCATOR.allocate( void *Ptr = SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::Malloc,
size, scudo::Chunk::Origin::Malloc, SCUDO_MALLOC_ALIGNMENT)); SCUDO_MALLOC_ALIGNMENT);
reportAllocation(Ptr, size);
return scudo::setErrnoOnNull(Ptr);
} }
#if SCUDO_ANDROID #if SCUDO_ANDROID
@ -105,8 +139,10 @@ INTERFACE WEAK void *SCUDO_PREFIX(memalign)(size_t alignment, size_t size) {
scudo::reportAlignmentNotPowerOfTwo(alignment); scudo::reportAlignmentNotPowerOfTwo(alignment);
} }
} }
return SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::Memalign, void *Ptr =
alignment); SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::Memalign, alignment);
reportAllocation(Ptr, size);
return Ptr;
} }
INTERFACE WEAK int SCUDO_PREFIX(posix_memalign)(void **memptr, size_t alignment, INTERFACE WEAK int SCUDO_PREFIX(posix_memalign)(void **memptr, size_t alignment,
@ -120,6 +156,8 @@ INTERFACE WEAK int SCUDO_PREFIX(posix_memalign)(void **memptr, size_t alignment,
SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::Memalign, alignment); SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::Memalign, alignment);
if (UNLIKELY(!Ptr)) if (UNLIKELY(!Ptr))
return ENOMEM; return ENOMEM;
reportAllocation(Ptr, size);
*memptr = Ptr; *memptr = Ptr;
return 0; return 0;
} }
@ -134,26 +172,57 @@ INTERFACE WEAK void *SCUDO_PREFIX(pvalloc)(size_t size) {
scudo::reportPvallocOverflow(size); scudo::reportPvallocOverflow(size);
} }
// pvalloc(0) should allocate one page. // pvalloc(0) should allocate one page.
return scudo::setErrnoOnNull( void *Ptr =
SCUDO_ALLOCATOR.allocate(size ? scudo::roundUp(size, PageSize) : PageSize, SCUDO_ALLOCATOR.allocate(size ? scudo::roundUp(size, PageSize) : PageSize,
scudo::Chunk::Origin::Memalign, PageSize)); scudo::Chunk::Origin::Memalign, PageSize);
reportAllocation(Ptr, scudo::roundUp(size, PageSize));
return scudo::setErrnoOnNull(Ptr);
} }
INTERFACE WEAK void *SCUDO_PREFIX(realloc)(void *ptr, size_t size) { INTERFACE WEAK void *SCUDO_PREFIX(realloc)(void *ptr, size_t size) {
if (!ptr) if (!ptr) {
return scudo::setErrnoOnNull(SCUDO_ALLOCATOR.allocate( void *Ptr = SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::Malloc,
size, scudo::Chunk::Origin::Malloc, SCUDO_MALLOC_ALIGNMENT)); SCUDO_MALLOC_ALIGNMENT);
reportAllocation(Ptr, size);
return scudo::setErrnoOnNull(Ptr);
}
if (size == 0) { if (size == 0) {
reportDeallocation(ptr);
SCUDO_ALLOCATOR.deallocate(ptr, scudo::Chunk::Origin::Malloc); SCUDO_ALLOCATOR.deallocate(ptr, scudo::Chunk::Origin::Malloc);
return nullptr; return nullptr;
} }
return scudo::setErrnoOnNull(
SCUDO_ALLOCATOR.reallocate(ptr, size, SCUDO_MALLOC_ALIGNMENT)); // Given that the reporting of deallocation and allocation are not atomic, we
// always pretend the old pointer will be released so that the user doesn't
// need to worry about the false double-use case from the view of hooks.
//
// For example, assume that `realloc` releases the old pointer and allocates a
// new pointer. Before the reporting of both operations has been done, another
// thread may get the old pointer from `malloc`. It may be misinterpreted as
// double-use if it's not handled properly on the hook side.
reportReallocDeallocation(ptr);
void *NewPtr = SCUDO_ALLOCATOR.reallocate(ptr, size, SCUDO_MALLOC_ALIGNMENT);
if (NewPtr != nullptr) {
// Note that even if NewPtr == ptr, the size has changed. We still need to
// report the new size.
reportReallocAllocation(/*OldPtr=*/ptr, NewPtr, size);
} else {
// If `realloc` fails, the old pointer is not released. Report the old
// pointer as allocated again.
reportReallocAllocation(/*OldPtr=*/ptr, /*NewPtr=*/ptr,
SCUDO_ALLOCATOR.getAllocSize(ptr));
}
return scudo::setErrnoOnNull(NewPtr);
} }
INTERFACE WEAK void *SCUDO_PREFIX(valloc)(size_t size) { INTERFACE WEAK void *SCUDO_PREFIX(valloc)(size_t size) {
return scudo::setErrnoOnNull(SCUDO_ALLOCATOR.allocate( void *Ptr = SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::Memalign,
size, scudo::Chunk::Origin::Memalign, scudo::getPageSizeCached())); scudo::getPageSizeCached());
reportAllocation(Ptr, size);
return scudo::setErrnoOnNull(Ptr);
} }
INTERFACE WEAK int SCUDO_PREFIX(malloc_iterate)( INTERFACE WEAK int SCUDO_PREFIX(malloc_iterate)(
@ -198,6 +267,7 @@ INTERFACE WEAK int SCUDO_PREFIX(mallopt)(int param, int value) {
return 1; return 1;
} else if (param == M_LOG_STATS) { } else if (param == M_LOG_STATS) {
SCUDO_ALLOCATOR.printStats(); SCUDO_ALLOCATOR.printStats();
SCUDO_ALLOCATOR.printFragmentationInfo();
return 1; return 1;
} else { } else {
scudo::Option option; scudo::Option option;
@ -233,8 +303,12 @@ INTERFACE WEAK void *SCUDO_PREFIX(aligned_alloc)(size_t alignment,
} }
scudo::reportInvalidAlignedAllocAlignment(alignment, size); scudo::reportInvalidAlignedAllocAlignment(alignment, size);
} }
return scudo::setErrnoOnNull(
SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::Malloc, alignment)); void *Ptr =
SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::Malloc, alignment);
reportAllocation(Ptr, size);
return scudo::setErrnoOnNull(Ptr);
} }
INTERFACE WEAK int SCUDO_PREFIX(malloc_info)(UNUSED int options, FILE *stream) { INTERFACE WEAK int SCUDO_PREFIX(malloc_info)(UNUSED int options, FILE *stream) {

View file

@ -12,6 +12,9 @@
#if SCUDO_ANDROID && _BIONIC #if SCUDO_ANDROID && _BIONIC
#include "allocator_config.h" #include "allocator_config.h"
#include "internal_defs.h"
#include "platform.h"
#include "scudo/interface.h"
#include "wrappers_c.h" #include "wrappers_c.h"
#include "wrappers_c_checks.h" #include "wrappers_c_checks.h"
@ -24,7 +27,7 @@
extern "C" void SCUDO_PREFIX(malloc_postinit)(); extern "C" void SCUDO_PREFIX(malloc_postinit)();
SCUDO_REQUIRE_CONSTANT_INITIALIZATION SCUDO_REQUIRE_CONSTANT_INITIALIZATION
static scudo::Allocator<scudo::AndroidConfig, SCUDO_PREFIX(malloc_postinit)> static scudo::Allocator<scudo::Config, SCUDO_PREFIX(malloc_postinit)>
SCUDO_ALLOCATOR; SCUDO_ALLOCATOR;
#include "wrappers_c.inc" #include "wrappers_c.inc"
@ -35,15 +38,15 @@ static scudo::Allocator<scudo::AndroidConfig, SCUDO_PREFIX(malloc_postinit)>
// TODO(kostyak): support both allocators. // TODO(kostyak): support both allocators.
INTERFACE void __scudo_print_stats(void) { Allocator.printStats(); } INTERFACE void __scudo_print_stats(void) { Allocator.printStats(); }
INTERFACE void INTERFACE void __scudo_get_error_info(
__scudo_get_error_info(struct scudo_error_info *error_info, struct scudo_error_info *error_info, uintptr_t fault_addr,
uintptr_t fault_addr, const char *stack_depot, const char *stack_depot, size_t stack_depot_size, const char *region_info,
const char *region_info, const char *ring_buffer, const char *ring_buffer, size_t ring_buffer_size, const char *memory,
const char *memory, const char *memory_tags, const char *memory_tags, uintptr_t memory_addr, size_t memory_size) {
uintptr_t memory_addr, size_t memory_size) { (void)(stack_depot_size);
Allocator.getErrorInfo(error_info, fault_addr, stack_depot, region_info, Allocator.getErrorInfo(error_info, fault_addr, stack_depot, region_info,
ring_buffer, memory, memory_tags, memory_addr, ring_buffer, ring_buffer_size, memory, memory_tags,
memory_size); memory_addr, memory_size);
} }
INTERFACE const char *__scudo_get_stack_depot_addr() { INTERFACE const char *__scudo_get_stack_depot_addr() {

View file

@ -12,6 +12,9 @@
#if !SCUDO_ANDROID || !_BIONIC #if !SCUDO_ANDROID || !_BIONIC
#include "allocator_config.h" #include "allocator_config.h"
#include "internal_defs.h"
#include "platform.h"
#include "scudo/interface.h"
#include "wrappers_c.h" #include "wrappers_c.h"
#include <stdint.h> #include <stdint.h>
@ -21,86 +24,125 @@ struct nothrow_t {};
enum class align_val_t : size_t {}; enum class align_val_t : size_t {};
} // namespace std } // namespace std
static void reportAllocation(void *ptr, size_t size) {
if (SCUDO_ENABLE_HOOKS)
if (__scudo_allocate_hook && ptr)
__scudo_allocate_hook(ptr, size);
}
static void reportDeallocation(void *ptr) {
if (SCUDO_ENABLE_HOOKS)
if (__scudo_deallocate_hook)
__scudo_deallocate_hook(ptr);
}
INTERFACE WEAK void *operator new(size_t size) { INTERFACE WEAK void *operator new(size_t size) {
return Allocator.allocate(size, scudo::Chunk::Origin::New); void *Ptr = Allocator.allocate(size, scudo::Chunk::Origin::New);
reportAllocation(Ptr, size);
return Ptr;
} }
INTERFACE WEAK void *operator new[](size_t size) { INTERFACE WEAK void *operator new[](size_t size) {
return Allocator.allocate(size, scudo::Chunk::Origin::NewArray); void *Ptr = Allocator.allocate(size, scudo::Chunk::Origin::NewArray);
reportAllocation(Ptr, size);
return Ptr;
} }
INTERFACE WEAK void *operator new(size_t size, INTERFACE WEAK void *operator new(size_t size,
std::nothrow_t const &) NOEXCEPT { std::nothrow_t const &) NOEXCEPT {
return Allocator.allocate(size, scudo::Chunk::Origin::New); void *Ptr = Allocator.allocate(size, scudo::Chunk::Origin::New);
reportAllocation(Ptr, size);
return Ptr;
} }
INTERFACE WEAK void *operator new[](size_t size, INTERFACE WEAK void *operator new[](size_t size,
std::nothrow_t const &) NOEXCEPT { std::nothrow_t const &) NOEXCEPT {
return Allocator.allocate(size, scudo::Chunk::Origin::NewArray); void *Ptr = Allocator.allocate(size, scudo::Chunk::Origin::NewArray);
reportAllocation(Ptr, size);
return Ptr;
} }
INTERFACE WEAK void *operator new(size_t size, std::align_val_t align) { INTERFACE WEAK void *operator new(size_t size, std::align_val_t align) {
return Allocator.allocate(size, scudo::Chunk::Origin::New, void *Ptr = Allocator.allocate(size, scudo::Chunk::Origin::New,
static_cast<scudo::uptr>(align)); static_cast<scudo::uptr>(align));
reportAllocation(Ptr, size);
return Ptr;
} }
INTERFACE WEAK void *operator new[](size_t size, std::align_val_t align) { INTERFACE WEAK void *operator new[](size_t size, std::align_val_t align) {
return Allocator.allocate(size, scudo::Chunk::Origin::NewArray, void *Ptr = Allocator.allocate(size, scudo::Chunk::Origin::NewArray,
static_cast<scudo::uptr>(align)); static_cast<scudo::uptr>(align));
reportAllocation(Ptr, size);
return Ptr;
} }
INTERFACE WEAK void *operator new(size_t size, std::align_val_t align, INTERFACE WEAK void *operator new(size_t size, std::align_val_t align,
std::nothrow_t const &) NOEXCEPT { std::nothrow_t const &) NOEXCEPT {
return Allocator.allocate(size, scudo::Chunk::Origin::New, void *Ptr = Allocator.allocate(size, scudo::Chunk::Origin::New,
static_cast<scudo::uptr>(align)); static_cast<scudo::uptr>(align));
reportAllocation(Ptr, size);
return Ptr;
} }
INTERFACE WEAK void *operator new[](size_t size, std::align_val_t align, INTERFACE WEAK void *operator new[](size_t size, std::align_val_t align,
std::nothrow_t const &) NOEXCEPT { std::nothrow_t const &) NOEXCEPT {
return Allocator.allocate(size, scudo::Chunk::Origin::NewArray, void *Ptr = Allocator.allocate(size, scudo::Chunk::Origin::NewArray,
static_cast<scudo::uptr>(align)); static_cast<scudo::uptr>(align));
reportAllocation(Ptr, size);
return Ptr;
} }
INTERFACE WEAK void operator delete(void *ptr) NOEXCEPT { INTERFACE WEAK void operator delete(void *ptr) NOEXCEPT {
reportDeallocation(ptr);
Allocator.deallocate(ptr, scudo::Chunk::Origin::New); Allocator.deallocate(ptr, scudo::Chunk::Origin::New);
} }
INTERFACE WEAK void operator delete[](void *ptr) NOEXCEPT { INTERFACE WEAK void operator delete[](void *ptr) NOEXCEPT {
reportDeallocation(ptr);
Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray); Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray);
} }
INTERFACE WEAK void operator delete(void *ptr, INTERFACE WEAK void operator delete(void *ptr,
std::nothrow_t const &) NOEXCEPT { std::nothrow_t const &) NOEXCEPT {
reportDeallocation(ptr);
Allocator.deallocate(ptr, scudo::Chunk::Origin::New); Allocator.deallocate(ptr, scudo::Chunk::Origin::New);
} }
INTERFACE WEAK void operator delete[](void *ptr, INTERFACE WEAK void operator delete[](void *ptr,
std::nothrow_t const &) NOEXCEPT { std::nothrow_t const &) NOEXCEPT {
reportDeallocation(ptr);
Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray); Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray);
} }
INTERFACE WEAK void operator delete(void *ptr, size_t size) NOEXCEPT { INTERFACE WEAK void operator delete(void *ptr, size_t size) NOEXCEPT {
reportDeallocation(ptr);
Allocator.deallocate(ptr, scudo::Chunk::Origin::New, size); Allocator.deallocate(ptr, scudo::Chunk::Origin::New, size);
} }
INTERFACE WEAK void operator delete[](void *ptr, size_t size) NOEXCEPT { INTERFACE WEAK void operator delete[](void *ptr, size_t size) NOEXCEPT {
reportDeallocation(ptr);
Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, size); Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, size);
} }
INTERFACE WEAK void operator delete(void *ptr, INTERFACE WEAK void operator delete(void *ptr,
std::align_val_t align) NOEXCEPT { std::align_val_t align) NOEXCEPT {
reportDeallocation(ptr);
Allocator.deallocate(ptr, scudo::Chunk::Origin::New, 0, Allocator.deallocate(ptr, scudo::Chunk::Origin::New, 0,
static_cast<scudo::uptr>(align)); static_cast<scudo::uptr>(align));
} }
INTERFACE WEAK void operator delete[](void *ptr, INTERFACE WEAK void operator delete[](void *ptr,
std::align_val_t align) NOEXCEPT { std::align_val_t align) NOEXCEPT {
reportDeallocation(ptr);
Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, 0, Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, 0,
static_cast<scudo::uptr>(align)); static_cast<scudo::uptr>(align));
} }
INTERFACE WEAK void operator delete(void *ptr, std::align_val_t align, INTERFACE WEAK void operator delete(void *ptr, std::align_val_t align,
std::nothrow_t const &) NOEXCEPT { std::nothrow_t const &) NOEXCEPT {
reportDeallocation(ptr);
Allocator.deallocate(ptr, scudo::Chunk::Origin::New, 0, Allocator.deallocate(ptr, scudo::Chunk::Origin::New, 0,
static_cast<scudo::uptr>(align)); static_cast<scudo::uptr>(align));
} }
INTERFACE WEAK void operator delete[](void *ptr, std::align_val_t align, INTERFACE WEAK void operator delete[](void *ptr, std::align_val_t align,
std::nothrow_t const &) NOEXCEPT { std::nothrow_t const &) NOEXCEPT {
reportDeallocation(ptr);
Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, 0, Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, 0,
static_cast<scudo::uptr>(align)); static_cast<scudo::uptr>(align));
} }
INTERFACE WEAK void operator delete(void *ptr, size_t size, INTERFACE WEAK void operator delete(void *ptr, size_t size,
std::align_val_t align) NOEXCEPT { std::align_val_t align) NOEXCEPT {
reportDeallocation(ptr);
Allocator.deallocate(ptr, scudo::Chunk::Origin::New, size, Allocator.deallocate(ptr, scudo::Chunk::Origin::New, size,
static_cast<scudo::uptr>(align)); static_cast<scudo::uptr>(align));
} }
INTERFACE WEAK void operator delete[](void *ptr, size_t size, INTERFACE WEAK void operator delete[](void *ptr, size_t size,
std::align_val_t align) NOEXCEPT { std::align_val_t align) NOEXCEPT {
reportDeallocation(ptr);
Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, size, Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, size,
static_cast<scudo::uptr>(align)); static_cast<scudo::uptr>(align));
} }