193 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			193 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			C++
		
	
	
	
//===-- scudo_allocator_secondary.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
 | 
						|
//
 | 
						|
//===----------------------------------------------------------------------===//
 | 
						|
///
 | 
						|
/// Scudo Secondary Allocator.
 | 
						|
/// This services allocation that are too large to be serviced by the Primary
 | 
						|
/// Allocator. It is directly backed by the memory mapping functions of the
 | 
						|
/// operating system.
 | 
						|
///
 | 
						|
//===----------------------------------------------------------------------===//
 | 
						|
 | 
						|
#ifndef SCUDO_ALLOCATOR_SECONDARY_H_
 | 
						|
#define SCUDO_ALLOCATOR_SECONDARY_H_
 | 
						|
 | 
						|
#ifndef SCUDO_ALLOCATOR_H_
 | 
						|
# error "This file must be included inside scudo_allocator.h."
 | 
						|
#endif
 | 
						|
 | 
						|
// Secondary backed allocations are standalone chunks that contain extra
 | 
						|
// information stored in a LargeChunk::Header prior to the frontend's header.
 | 
						|
//
 | 
						|
// The secondary takes care of alignment requirements (so that it can release
 | 
						|
// unnecessary pages in the rare event of larger alignments), and as such must
 | 
						|
// know about the frontend's header size.
 | 
						|
//
 | 
						|
// Since Windows doesn't support partial releasing of a reserved memory region,
 | 
						|
// we have to keep track of both the reserved and the committed memory.
 | 
						|
//
 | 
						|
// The resulting chunk resembles the following:
 | 
						|
//
 | 
						|
//   +--------------------+
 | 
						|
//   | Guard page(s)      |
 | 
						|
//   +--------------------+
 | 
						|
//   | Unused space*      |
 | 
						|
//   +--------------------+
 | 
						|
//   | LargeChunk::Header |
 | 
						|
//   +--------------------+
 | 
						|
//   | {Unp,P}ackedHeader |
 | 
						|
//   +--------------------+
 | 
						|
//   | Data (aligned)     |
 | 
						|
//   +--------------------+
 | 
						|
//   | Unused space**     |
 | 
						|
//   +--------------------+
 | 
						|
//   | Guard page(s)      |
 | 
						|
//   +--------------------+
 | 
						|
 | 
						|
namespace LargeChunk {
 | 
						|
struct Header {
 | 
						|
  ReservedAddressRange StoredRange;
 | 
						|
  uptr CommittedSize;
 | 
						|
  uptr Size;
 | 
						|
};
 | 
						|
constexpr uptr getHeaderSize() {
 | 
						|
  return RoundUpTo(sizeof(Header), MinAlignment);
 | 
						|
}
 | 
						|
static Header *getHeader(uptr Ptr) {
 | 
						|
  return reinterpret_cast<Header *>(Ptr - getHeaderSize());
 | 
						|
}
 | 
						|
static Header *getHeader(const void *Ptr) {
 | 
						|
  return getHeader(reinterpret_cast<uptr>(Ptr));
 | 
						|
}
 | 
						|
}  // namespace LargeChunk
 | 
						|
 | 
						|
class LargeMmapAllocator {
 | 
						|
 public:
 | 
						|
  void Init() {
 | 
						|
    internal_memset(this, 0, sizeof(*this));
 | 
						|
  }
 | 
						|
 | 
						|
  void *Allocate(AllocatorStats *Stats, uptr Size, uptr Alignment) {
 | 
						|
    const uptr UserSize = Size - Chunk::getHeaderSize();
 | 
						|
    // The Scudo frontend prevents us from allocating more than
 | 
						|
    // MaxAllowedMallocSize, so integer overflow checks would be superfluous.
 | 
						|
    uptr ReservedSize = Size + LargeChunk::getHeaderSize();
 | 
						|
    if (UNLIKELY(Alignment > MinAlignment))
 | 
						|
      ReservedSize += Alignment;
 | 
						|
    const uptr PageSize = GetPageSizeCached();
 | 
						|
    ReservedSize = RoundUpTo(ReservedSize, PageSize);
 | 
						|
    // Account for 2 guard pages, one before and one after the chunk.
 | 
						|
    ReservedSize += 2 * PageSize;
 | 
						|
 | 
						|
    ReservedAddressRange AddressRange;
 | 
						|
    uptr ReservedBeg = AddressRange.Init(ReservedSize, SecondaryAllocatorName);
 | 
						|
    if (UNLIKELY(ReservedBeg == ~static_cast<uptr>(0)))
 | 
						|
      return nullptr;
 | 
						|
    // A page-aligned pointer is assumed after that, so check it now.
 | 
						|
    DCHECK(IsAligned(ReservedBeg, PageSize));
 | 
						|
    uptr ReservedEnd = ReservedBeg + ReservedSize;
 | 
						|
    // The beginning of the user area for that allocation comes after the
 | 
						|
    // initial guard page, and both headers. This is the pointer that has to
 | 
						|
    // abide by alignment requirements.
 | 
						|
    uptr CommittedBeg = ReservedBeg + PageSize;
 | 
						|
    uptr UserBeg = CommittedBeg + HeadersSize;
 | 
						|
    uptr UserEnd = UserBeg + UserSize;
 | 
						|
    uptr CommittedEnd = RoundUpTo(UserEnd, PageSize);
 | 
						|
 | 
						|
    // In the rare event of larger alignments, we will attempt to fit the mmap
 | 
						|
    // area better and unmap extraneous memory. This will also ensure that the
 | 
						|
    // offset and unused bytes field of the header stay small.
 | 
						|
    if (UNLIKELY(Alignment > MinAlignment)) {
 | 
						|
      if (!IsAligned(UserBeg, Alignment)) {
 | 
						|
        UserBeg = RoundUpTo(UserBeg, Alignment);
 | 
						|
        CommittedBeg = RoundDownTo(UserBeg - HeadersSize, PageSize);
 | 
						|
        const uptr NewReservedBeg = CommittedBeg - PageSize;
 | 
						|
        DCHECK_GE(NewReservedBeg, ReservedBeg);
 | 
						|
        if (!SANITIZER_WINDOWS && NewReservedBeg != ReservedBeg) {
 | 
						|
          AddressRange.Unmap(ReservedBeg, NewReservedBeg - ReservedBeg);
 | 
						|
          ReservedBeg = NewReservedBeg;
 | 
						|
        }
 | 
						|
        UserEnd = UserBeg + UserSize;
 | 
						|
        CommittedEnd = RoundUpTo(UserEnd, PageSize);
 | 
						|
      }
 | 
						|
      const uptr NewReservedEnd = CommittedEnd + PageSize;
 | 
						|
      DCHECK_LE(NewReservedEnd, ReservedEnd);
 | 
						|
      if (!SANITIZER_WINDOWS && NewReservedEnd != ReservedEnd) {
 | 
						|
        AddressRange.Unmap(NewReservedEnd, ReservedEnd - NewReservedEnd);
 | 
						|
        ReservedEnd = NewReservedEnd;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    DCHECK_LE(UserEnd, CommittedEnd);
 | 
						|
    const uptr CommittedSize = CommittedEnd - CommittedBeg;
 | 
						|
    // Actually mmap the memory, preserving the guard pages on either sides.
 | 
						|
    CHECK_EQ(CommittedBeg, AddressRange.Map(CommittedBeg, CommittedSize));
 | 
						|
    const uptr Ptr = UserBeg - Chunk::getHeaderSize();
 | 
						|
    LargeChunk::Header *H = LargeChunk::getHeader(Ptr);
 | 
						|
    H->StoredRange = AddressRange;
 | 
						|
    H->Size = CommittedEnd - Ptr;
 | 
						|
    H->CommittedSize = CommittedSize;
 | 
						|
 | 
						|
    // The primary adds the whole class size to the stats when allocating a
 | 
						|
    // chunk, so we will do something similar here. But we will not account for
 | 
						|
    // the guard pages.
 | 
						|
    {
 | 
						|
      SpinMutexLock l(&StatsMutex);
 | 
						|
      Stats->Add(AllocatorStatAllocated, CommittedSize);
 | 
						|
      Stats->Add(AllocatorStatMapped, CommittedSize);
 | 
						|
      AllocatedBytes += CommittedSize;
 | 
						|
      if (LargestSize < CommittedSize)
 | 
						|
        LargestSize = CommittedSize;
 | 
						|
      NumberOfAllocs++;
 | 
						|
    }
 | 
						|
 | 
						|
    return reinterpret_cast<void *>(Ptr);
 | 
						|
  }
 | 
						|
 | 
						|
  void Deallocate(AllocatorStats *Stats, void *Ptr) {
 | 
						|
    LargeChunk::Header *H = LargeChunk::getHeader(Ptr);
 | 
						|
    // Since we're unmapping the entirety of where the ReservedAddressRange
 | 
						|
    // actually is, copy onto the stack.
 | 
						|
    ReservedAddressRange AddressRange = H->StoredRange;
 | 
						|
    const uptr Size = H->CommittedSize;
 | 
						|
    {
 | 
						|
      SpinMutexLock l(&StatsMutex);
 | 
						|
      Stats->Sub(AllocatorStatAllocated, Size);
 | 
						|
      Stats->Sub(AllocatorStatMapped, Size);
 | 
						|
      FreedBytes += Size;
 | 
						|
      NumberOfFrees++;
 | 
						|
    }
 | 
						|
    AddressRange.Unmap(reinterpret_cast<uptr>(AddressRange.base()),
 | 
						|
                       AddressRange.size());
 | 
						|
  }
 | 
						|
 | 
						|
  static uptr GetActuallyAllocatedSize(void *Ptr) {
 | 
						|
    return LargeChunk::getHeader(Ptr)->Size;
 | 
						|
  }
 | 
						|
 | 
						|
  void PrintStats() {
 | 
						|
    Printf("Stats: LargeMmapAllocator: allocated %zd times (%zd K), "
 | 
						|
           "freed %zd times (%zd K), remains %zd (%zd K) max %zd M\n",
 | 
						|
           NumberOfAllocs, AllocatedBytes >> 10, NumberOfFrees,
 | 
						|
           FreedBytes >> 10, NumberOfAllocs - NumberOfFrees,
 | 
						|
           (AllocatedBytes - FreedBytes) >> 10, LargestSize >> 20);
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  static constexpr uptr HeadersSize =
 | 
						|
      LargeChunk::getHeaderSize() + Chunk::getHeaderSize();
 | 
						|
 | 
						|
  StaticSpinMutex StatsMutex;
 | 
						|
  u32 NumberOfAllocs;
 | 
						|
  u32 NumberOfFrees;
 | 
						|
  uptr AllocatedBytes;
 | 
						|
  uptr FreedBytes;
 | 
						|
  uptr LargestSize;
 | 
						|
};
 | 
						|
 | 
						|
#endif  // SCUDO_ALLOCATOR_SECONDARY_H_
 |