#include "base/metrics/persistent_memory_allocator.h"
#include <assert.h>
#include <algorithm>
#include <atomic>
#include <optional>
#include <string_view>
#include "base/bits.h"
#include "base/compiler_specific.h"
#include "base/containers/contains.h"
#include "base/debug/alias.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/persistent_histogram_allocator.h"
#include "base/metrics/sparse_histogram.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/strcat.h"
#include "base/system/sys_info.h"
#include "base/threading/scoped_blocking_call.h"
#include "build/build_config.h"
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#include "base/win/winbase_shim.h"
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
#include <sys/mman.h>
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_ARKWEB)
#include <sys/prctl.h>
#endif
#endif
#define PMA "PMA-DBG"
namespace {
constexpr uint32_t kSegmentMaxSize = 1 << 30;
constexpr uint32_t kGlobalCookie = 0x408305DC;
constexpr uint32_t kGlobalVersion = 3;
static constexpr uint32_t kOldCompatibleVersions[] = {2};
constexpr uint32_t kBlockCookieFree = 0;
constexpr uint32_t kBlockCookieQueue = 1;
constexpr uint32_t kBlockCookieWasted = 0x4B594F52;
constexpr uint32_t kBlockCookieAllocated = 0xC8799269;
constexpr uint32_t kFlagCorrupt = 1 << 0;
constexpr uint32_t kFlagFull = 1 << 1;
enum AllocatorError : int {
kMemoryIsCorrupt = 1,
kMaxValue = kMemoryIsCorrupt,
};
bool CheckFlag(const volatile std::atomic<uint32_t>* flags, uint32_t flag) {
uint32_t loaded_flags = flags->load(std::memory_order_relaxed);
return (loaded_flags & flag) != 0;
}
void SetFlag(volatile std::atomic<uint32_t>* flags, uint32_t flag) {
uint32_t loaded_flags = flags->load(std::memory_order_relaxed);
for (;;) {
uint32_t new_flags = (loaded_flags & ~flag) | flag;
if (flags->compare_exchange_weak(loaded_flags, new_flags,
std::memory_order_relaxed,
std::memory_order_relaxed)) {
break;
}
}
}
}
namespace base {
struct PersistentMemoryAllocator::BlockHeader {
uint32_t size;
uint32_t cookie;
std::atomic<uint32_t> type_id;
std::atomic<uint32_t> next;
};
struct PersistentMemoryAllocator::SharedMetadata {
uint32_t cookie;
uint32_t size;
uint32_t page_size;
uint32_t version;
uint64_t id;
uint32_t name;
uint32_t padding1;
volatile std::atomic<uint8_t> memory_state;
uint8_t padding2[3];
volatile std::atomic<uint32_t> flags;
volatile std::atomic<uint32_t> freeptr;
volatile std::atomic<uint32_t> tailptr;
volatile BlockHeader queue;
};
const PersistentMemoryAllocator::Reference
PersistentMemoryAllocator::kReferenceQueue =
offsetof(SharedMetadata, queue);
const base::FilePath::CharType PersistentMemoryAllocator::kFileExtension[] =
FILE_PATH_LITERAL(".pma");
PersistentMemoryAllocator::Iterator::Iterator(
const PersistentMemoryAllocator* allocator)
: allocator_(allocator), last_record_(kReferenceQueue), record_count_(0) {}
PersistentMemoryAllocator::Iterator::Iterator(
const PersistentMemoryAllocator* allocator,
Reference starting_after)
: allocator_(allocator), last_record_(0), record_count_(0) {
Reset(starting_after);
}
PersistentMemoryAllocator::Iterator::~Iterator() = default;
void PersistentMemoryAllocator::Iterator::Reset() {
last_record_.store(kReferenceQueue, std::memory_order_relaxed);
record_count_.store(0, std::memory_order_relaxed);
}
void PersistentMemoryAllocator::Iterator::Reset(Reference starting_after) {
if (starting_after == 0) {
Reset();
return;
}
last_record_.store(starting_after, std::memory_order_relaxed);
record_count_.store(0, std::memory_order_relaxed);
const volatile BlockHeader* block =
allocator_->GetBlock(starting_after, 0, 0,
false, false);
if (!block || block->next.load(std::memory_order_relaxed) == 0) {
allocator_->DumpWithoutCrashing(starting_after,
0,
0,
true);
}
}
PersistentMemoryAllocator::Reference
PersistentMemoryAllocator::Iterator::GetLast() {
Reference last = last_record_.load(std::memory_order_relaxed);
if (last == kReferenceQueue) {
return kReferenceNull;
}
return last;
}
PersistentMemoryAllocator::Reference
PersistentMemoryAllocator::Iterator::GetNext(uint32_t* type_return,
size_t* alloc_size) {
uint32_t count = record_count_.load(std::memory_order_acquire);
Reference last = last_record_.load(std::memory_order_acquire);
Reference next = 0;
size_t next_size = 0;
while (true) {
const volatile BlockHeader* block =
allocator_->GetBlock(last, 0, 0,
true, false);
if (!block) {
return kReferenceNull;
}
next = block->next.load(std::memory_order_acquire);
if (next == kReferenceQueue) {
return kReferenceNull;
}
block = allocator_->GetBlock(next, 0, 0,
false, false,
&next_size);
if (!block) {
allocator_->SetCorrupt();
return kReferenceNull;
}
if (last_record_.compare_exchange_strong(
last, next, std::memory_order_acq_rel, std::memory_order_acquire)) {
*type_return = block->type_id.load(std::memory_order_relaxed);
break;
}
}
const uint32_t freeptr = std::min(
allocator_->shared_meta()->freeptr.load(std::memory_order_relaxed),
allocator_->mem_size_);
const uint32_t max_records =
freeptr / (sizeof(BlockHeader) + kAllocAlignment);
if (count > max_records) {
allocator_->SetCorrupt();
return kReferenceNull;
}
record_count_.fetch_add(1, std::memory_order_release);
if (alloc_size) {
*alloc_size = next_size;
}
return next;
}
PersistentMemoryAllocator::Reference
PersistentMemoryAllocator::Iterator::GetNextOfType(uint32_t type_match,
size_t* alloc_size) {
Reference ref;
size_t size;
uint32_t type_found;
while ((ref = GetNext(&type_found, &size)) != 0) {
if (type_found == type_match) {
if (alloc_size) {
*alloc_size = size;
}
return ref;
}
}
return kReferenceNull;
}
bool PersistentMemoryAllocator::IsMemoryAcceptable(const void* base,
size_t size,
size_t page_size,
bool readonly) {
return ((base && reinterpret_cast<uintptr_t>(base) % kAllocAlignment == 0) &&
(size >= sizeof(SharedMetadata) && size <= kSegmentMaxSize) &&
(size % kAllocAlignment == 0 || readonly) &&
(page_size == 0 || size % page_size == 0 || readonly));
}
PersistentMemoryAllocator::PersistentMemoryAllocator(void* base,
size_t size,
size_t page_size,
uint64_t id,
std::string_view name,
AccessMode access_mode)
: PersistentMemoryAllocator(Memory(base, MEM_EXTERNAL),
size,
page_size,
id,
name,
access_mode) {}
PersistentMemoryAllocator::PersistentMemoryAllocator(Memory memory,
size_t size,
size_t page_size,
uint64_t id,
std::string_view name,
AccessMode access_mode)
: mem_base_(static_cast<char*>(memory.base)),
mem_type_(memory.type),
mem_size_(checked_cast<uint32_t>(size)),
mem_page_(checked_cast<uint32_t>((page_size ? page_size : size))),
vm_page_size_(SysInfo::VMAllocationGranularity()),
access_mode_(access_mode) {
static_assert(sizeof(PersistentMemoryAllocator::BlockHeader) == 16,
"struct is not portable across different natural word widths");
static_assert(sizeof(PersistentMemoryAllocator::SharedMetadata) == 64,
"struct is not portable across different natural word widths");
static_assert(sizeof(BlockHeader) % kAllocAlignment == 0,
"BlockHeader is not a multiple of kAllocAlignment");
static_assert(sizeof(SharedMetadata) % kAllocAlignment == 0,
"SharedMetadata is not a multiple of kAllocAlignment");
static_assert(kReferenceQueue % kAllocAlignment == 0,
"\"queue\" is not aligned properly; must be at end of struct");
const bool readonly = access_mode == kReadOnly;
CHECK(IsMemoryAcceptable(memory.base, size, page_size, readonly));
DCHECK(SharedMetadata().freeptr.is_lock_free());
DCHECK(SharedMetadata().flags.is_lock_free());
DCHECK(BlockHeader().next.is_lock_free());
CHECK(corrupt_.is_lock_free());
const bool allow_write_for_set_corrupt = (access_mode == kReadWrite);
if (shared_meta()->cookie != kGlobalCookie) {
if (access_mode != kReadWrite) {
SetCorrupt(allow_write_for_set_corrupt);
return;
}
volatile BlockHeader* const first_block =
UNSAFE_TODO(reinterpret_cast<volatile BlockHeader*>(
mem_base_ + sizeof(SharedMetadata)));
if (shared_meta()->cookie != 0 || shared_meta()->size != 0 ||
shared_meta()->version != 0 ||
shared_meta()->freeptr.load(std::memory_order_relaxed) != 0 ||
shared_meta()->flags.load(std::memory_order_relaxed) != 0 ||
shared_meta()->id != 0 || shared_meta()->name != 0 ||
shared_meta()->tailptr != 0 || shared_meta()->queue.cookie != 0 ||
shared_meta()->queue.next.load(std::memory_order_relaxed) != 0 ||
first_block->size != 0 || first_block->cookie != 0 ||
first_block->type_id.load(std::memory_order_relaxed) != 0 ||
first_block->next != 0) {
CHECK(allow_write_for_set_corrupt);
SetCorrupt(allow_write_for_set_corrupt);
}
shared_meta()->cookie = kGlobalCookie;
shared_meta()->size = mem_size_;
shared_meta()->page_size = mem_page_;
shared_meta()->version = kGlobalVersion;
shared_meta()->id = id;
uint32_t empty_freeptr = 0;
shared_meta()->freeptr.compare_exchange_strong(
empty_freeptr, sizeof(SharedMetadata),
std::memory_order_release,
std::memory_order_relaxed);
shared_meta()->queue.size = sizeof(BlockHeader);
shared_meta()->queue.cookie = kBlockCookieQueue;
shared_meta()->queue.next.store(kReferenceQueue, std::memory_order_release);
shared_meta()->tailptr.store(kReferenceQueue, std::memory_order_release);
if (!name.empty()) {
const size_t name_length = name.length() + 1;
shared_meta()->name = Allocate(name_length, 0);
char* name_cstr = GetAsArray<char>(shared_meta()->name, 0, name_length);
if (name_cstr) {
UNSAFE_TODO(memcpy(name_cstr, name.data(), name.length()));
}
}
shared_meta()->memory_state.store(MEMORY_INITIALIZED,
std::memory_order_release);
} else {
if (shared_meta()->size == 0 ||
(shared_meta()->version != kGlobalVersion &&
!Contains(kOldCompatibleVersions, shared_meta()->version)) ||
shared_meta()->freeptr.load(std::memory_order_relaxed) == 0 ||
shared_meta()->tailptr == 0 || shared_meta()->queue.cookie == 0 ||
shared_meta()->queue.next.load(std::memory_order_relaxed) == 0) {
SetCorrupt(allow_write_for_set_corrupt);
}
if (!readonly) {
if (shared_meta()->size < mem_size_) {
*const_cast<uint32_t*>(&mem_size_) = shared_meta()->size;
}
if (shared_meta()->page_size < mem_page_) {
*const_cast<uint32_t*>(&mem_page_) = shared_meta()->page_size;
}
if (!IsMemoryAcceptable(memory.base, mem_size_, mem_page_, readonly)) {
SetCorrupt(allow_write_for_set_corrupt);
}
}
}
}
PersistentMemoryAllocator::~PersistentMemoryAllocator() = default;
uint64_t PersistentMemoryAllocator::Id() const {
return shared_meta()->id;
}
std::string_view PersistentMemoryAllocator::Name() const {
Reference name_ref = shared_meta()->name;
size_t alloc_size = 0;
const char* name_cstr = GetAsArray<char>(
name_ref, 0, PersistentMemoryAllocator::kSizeAny, &alloc_size);
return StringViewAt(name_cstr, 0, alloc_size);
}
void PersistentMemoryAllocator::CreateTrackingHistograms(
std::string_view name) {
if (name.empty() || access_mode_ == kReadOnly) {
return;
}
DCHECK(!used_histogram_);
used_histogram_ = LinearHistogram::FactoryGet(
base::StrCat({"UMA.PersistentAllocator.", name, ".UsedPct"}), 1, 101, 21,
HistogramBase::kUmaTargetedHistogramFlag);
}
void PersistentMemoryAllocator::Flush(bool sync) {
FlushPartial(used(), sync);
}
void PersistentMemoryAllocator::SetMemoryState(uint8_t memory_state) {
shared_meta()->memory_state.store(memory_state, std::memory_order_relaxed);
FlushPartial(sizeof(SharedMetadata), false);
}
uint8_t PersistentMemoryAllocator::GetMemoryState() const {
return shared_meta()->memory_state.load(std::memory_order_relaxed);
}
size_t PersistentMemoryAllocator::used() const {
return std::min(shared_meta()->freeptr.load(std::memory_order_relaxed),
mem_size_);
}
PersistentMemoryAllocator::Reference PersistentMemoryAllocator::GetAsReference(
const void* memory,
uint32_t type_id) const {
uintptr_t address = reinterpret_cast<uintptr_t>(memory);
if (address < reinterpret_cast<uintptr_t>(mem_base_)) {
return kReferenceNull;
}
uintptr_t offset = address - reinterpret_cast<uintptr_t>(mem_base_);
if (offset >= mem_size_ || offset < sizeof(BlockHeader)) {
return kReferenceNull;
}
Reference ref = static_cast<Reference>(offset) - sizeof(BlockHeader);
if (!GetBlockData(ref, type_id, kSizeAny)) {
return kReferenceNull;
}
return ref;
}
uint32_t PersistentMemoryAllocator::GetType(Reference ref) const {
const volatile BlockHeader* const block = GetBlock(
ref, 0, 0, false, false);
if (!block) {
return 0;
}
return block->type_id.load(std::memory_order_relaxed);
}
bool PersistentMemoryAllocator::ChangeType(Reference ref,
uint32_t to_type_id,
uint32_t from_type_id,
bool clear) {
DCHECK_NE(access_mode_, kReadOnly);
volatile BlockHeader* const block = GetBlock(
ref, 0, 0, false, false);
if (!block) {
return false;
}
if (clear) {
if (!block->type_id.compare_exchange_strong(
from_type_id, kTypeIdTransitioning, std::memory_order_acquire,
std::memory_order_acquire)) {
return false;
}
volatile std::atomic<int>* data =
UNSAFE_TODO(reinterpret_cast<volatile std::atomic<int>*>(
reinterpret_cast<volatile char*>(block) + sizeof(BlockHeader)));
const uint32_t words = (block->size - sizeof(BlockHeader)) / sizeof(int);
DCHECK_EQ(0U, (block->size - sizeof(BlockHeader)) % sizeof(int));
for (uint32_t i = 0; i < words; ++i) {
data->store(0, std::memory_order_release);
UNSAFE_TODO(++data);
}
if (to_type_id == kTypeIdTransitioning) {
return true;
}
from_type_id = kTypeIdTransitioning;
bool success = block->type_id.compare_exchange_strong(
from_type_id, to_type_id, std::memory_order_release,
std::memory_order_relaxed);
DCHECK(success);
return success;
}
return block->type_id.compare_exchange_strong(from_type_id, to_type_id,
std::memory_order_acq_rel,
std::memory_order_acquire);
}
std::string_view PersistentMemoryAllocator::StringViewAt(const void* object,
size_t offset,
size_t alloc_size) {
if (!object || offset >= alloc_size) {
return "";
}
const char* const cstr =
UNSAFE_TODO(static_cast<const char*>(object) + offset);
return std::string_view(cstr,
UNSAFE_TODO(strnlen(cstr, alloc_size - offset - 1)));
}
PersistentMemoryAllocator::Reference PersistentMemoryAllocator::Allocate(
size_t req_size,
uint32_t type_id,
size_t* alloc_size) {
return AllocateImpl(req_size, type_id, alloc_size);
}
PersistentMemoryAllocator::Reference PersistentMemoryAllocator::AllocateImpl(
size_t req_size,
uint32_t type_id,
size_t* alloc_size) {
DCHECK_NE(access_mode_, kReadOnly);
if (req_size > kSegmentMaxSize - sizeof(BlockHeader)) {
this->DumpWithoutCrashing(0, type_id, req_size,
false);
return kReferenceNull;
}
size_t size = bits::AlignUp(req_size + sizeof(BlockHeader), kAllocAlignment);
if (size <= sizeof(BlockHeader) || size > mem_page_) {
this->DumpWithoutCrashing(0, type_id, req_size,
false);
return kReferenceNull;
}
uint32_t freeptr =
shared_meta()->freeptr.load(std::memory_order_acquire);
for (;;) {
if (IsCorrupt()) {
return kReferenceNull;
}
if (freeptr + size > mem_size_) {
SetFlag(&shared_meta()->flags, kFlagFull);
return kReferenceNull;
}
volatile BlockHeader* const block =
GetBlock(freeptr, 0, 0, false,
true);
if (!block) {
SetCorrupt();
return kReferenceNull;
}
const uint32_t page_free = mem_page_ - freeptr % mem_page_;
if (size > page_free) {
if (page_free <= sizeof(BlockHeader)) {
SetCorrupt();
return kReferenceNull;
}
const auto* allocator = GlobalHistogramAllocator::Get();
SCOPED_CRASH_KEY_STRING256(
PMA, "file_name",
allocator && allocator->HasPersistentLocation()
? allocator->GetPersistentLocation().BaseName().AsUTF8Unsafe()
: "N/A");
this->DumpWithoutCrashing(freeptr,
type_id,
req_size,
false);
const uint32_t new_freeptr = freeptr + page_free;
if (shared_meta()->freeptr.compare_exchange_strong(
freeptr, new_freeptr, std::memory_order_acq_rel,
std::memory_order_acquire)) {
block->size = page_free;
block->cookie = kBlockCookieWasted;
}
continue;
}
if (page_free - size < sizeof(BlockHeader) + kAllocAlignment) {
size = page_free;
if (freeptr + size > mem_size_) {
SetCorrupt();
return kReferenceNull;
}
}
const uint32_t new_freeptr = static_cast<uint32_t>(freeptr + size);
if (!shared_meta()->freeptr.compare_exchange_strong(
freeptr, new_freeptr, std::memory_order_acq_rel,
std::memory_order_acquire)) {
continue;
}
if (block->size != 0 || block->cookie != kBlockCookieFree ||
block->type_id.load(std::memory_order_relaxed) != 0 ||
block->next.load(std::memory_order_relaxed) != 0) {
SetCorrupt();
return kReferenceNull;
}
volatile char* mem_end =
UNSAFE_TODO(reinterpret_cast<volatile char*>(block) + size);
volatile char* mem_begin = reinterpret_cast<volatile char*>(
(reinterpret_cast<uintptr_t>(block) + sizeof(BlockHeader) +
(vm_page_size_ - 1)) &
~static_cast<uintptr_t>(vm_page_size_ - 1));
for (volatile char* memory = mem_begin; memory < mem_end;
UNSAFE_TODO(memory += vm_page_size_)) {
*memory = 0;
}
block->size = static_cast<uint32_t>(size);
block->cookie = kBlockCookieAllocated;
block->type_id.store(type_id, std::memory_order_relaxed);
if (alloc_size) {
*alloc_size = size - sizeof(BlockHeader);
}
return freeptr;
}
}
void PersistentMemoryAllocator::GetMemoryInfo(MemoryInfo* meminfo) const {
uint32_t remaining = std::max(
mem_size_ - shared_meta()->freeptr.load(std::memory_order_relaxed),
(uint32_t)sizeof(BlockHeader));
meminfo->total = mem_size_;
meminfo->free = remaining - sizeof(BlockHeader);
}
void PersistentMemoryAllocator::MakeIterable(Reference ref) {
DCHECK_NE(access_mode_, kReadOnly);
if (IsCorrupt()) {
return;
}
volatile BlockHeader* block = GetBlock(ref, 0, 0,
false, false);
if (!block) {
return;
}
Reference empty_ref = 0;
if (!block->next.compare_exchange_strong(
empty_ref, kReferenceQueue,
std::memory_order_acq_rel,
std::memory_order_acquire)) {
return;
}
uint32_t tail = shared_meta()->tailptr.load(std::memory_order_acquire);
for (;;) {
block = GetBlock(tail, 0, 0, true,
false);
if (!block) {
SetCorrupt();
return;
}
uint32_t next = kReferenceQueue;
if (block->next.compare_exchange_strong(
next, ref, std::memory_order_acq_rel, std::memory_order_acquire)) {
shared_meta()->tailptr.compare_exchange_strong(
tail, ref, std::memory_order_release, std::memory_order_relaxed);
return;
}
shared_meta()->tailptr.compare_exchange_strong(
tail, next, std::memory_order_acq_rel, std::memory_order_acquire);
}
}
void PersistentMemoryAllocator::SetCorrupt(bool allow_write) const {
if (!corrupt_.load(std::memory_order_relaxed) &&
!CheckFlag(
const_cast<volatile std::atomic<uint32_t>*>(&shared_meta()->flags),
kFlagCorrupt)) {
LOG(ERROR) << "Corruption detected in shared-memory segment.";
}
corrupt_.store(true, std::memory_order_relaxed);
if (allow_write && access_mode_ != kReadOnly) {
SetFlag(const_cast<volatile std::atomic<uint32_t>*>(&shared_meta()->flags),
kFlagCorrupt);
}
}
bool PersistentMemoryAllocator::IsCorrupt() const {
if (corrupt_.load(std::memory_order_relaxed)) {
return true;
}
if (CheckFlag(&shared_meta()->flags, kFlagCorrupt)) {
SetCorrupt(false);
return true;
}
return false;
}
bool PersistentMemoryAllocator::IsFull() const {
return CheckFlag(&shared_meta()->flags, kFlagFull);
}
const volatile PersistentMemoryAllocator::BlockHeader*
PersistentMemoryAllocator::GetBlock(Reference ref,
uint32_t type_id,
size_t size,
bool queue_ok,
bool free_ok,
size_t* alloc_size) const {
CHECK(!(alloc_size && (queue_ok || free_ok)));
if (ref == kReferenceQueue && queue_ok) {
return UNSAFE_TODO(
reinterpret_cast<const volatile BlockHeader*>(mem_base_ + ref));
}
if (ref < sizeof(SharedMetadata)) {
return nullptr;
}
if (ref % kAllocAlignment != 0) {
return nullptr;
}
size += sizeof(BlockHeader);
uint32_t total_size;
if (!base::CheckAdd(ref, size).AssignIfValid(&total_size)) {
return nullptr;
}
if (total_size > mem_size_) {
return nullptr;
}
const volatile BlockHeader* const block =
UNSAFE_TODO(reinterpret_cast<volatile BlockHeader*>(mem_base_ + ref));
if (!free_ok) {
if (block->cookie != kBlockCookieAllocated) {
return nullptr;
}
const uint32_t block_size = block->size;
if (block_size < size) {
return nullptr;
}
uint32_t block_end_ref;
if (!base::CheckAdd(ref, block_size).AssignIfValid(&block_end_ref)) {
return nullptr;
}
if (block_end_ref > mem_size_) {
SetCorrupt();
return nullptr;
}
if (type_id != 0 &&
block->type_id.load(std::memory_order_relaxed) != type_id) {
return nullptr;
}
if (alloc_size) {
*alloc_size = block_size - sizeof(BlockHeader);
}
}
return block;
}
void PersistentMemoryAllocator::FlushPartial(size_t length, bool sync) {
}
uint32_t PersistentMemoryAllocator::freeptr() const {
return shared_meta()->freeptr.load(std::memory_order_relaxed);
}
uint32_t PersistentMemoryAllocator::version() const {
return shared_meta()->version;
}
const volatile void* PersistentMemoryAllocator::GetBlockData(
Reference ref,
uint32_t type_id,
size_t size,
size_t* alloc_size) const {
DCHECK(size > 0);
const volatile BlockHeader* block = GetBlock(
ref, type_id, size, false, false, alloc_size);
if (!block) {
return nullptr;
}
return UNSAFE_TODO(reinterpret_cast<const volatile char*>(block) +
sizeof(BlockHeader));
}
void PersistentMemoryAllocator::UpdateTrackingHistograms() {
DCHECK_NE(access_mode_, kReadOnly);
if (used_histogram_) {
MemoryInfo meminfo;
GetMemoryInfo(&meminfo);
HistogramBase::Sample32 used_percent = static_cast<HistogramBase::Sample32>(
((meminfo.total - meminfo.free) * 100ULL / meminfo.total));
used_histogram_->Add(used_percent);
}
}
void PersistentMemoryAllocator::DumpWithoutCrashing(
[[maybe_unused]] Reference ref,
[[maybe_unused]] uint32_t expected_type,
[[maybe_unused]] size_t expected_size,
[[maybe_unused]] bool dump_block_header) const {
SCOPED_CRASH_KEY_STRING32(PMA, "name", Name());
SCOPED_CRASH_KEY_NUMBER(PMA, "memory_size", size());
SCOPED_CRASH_KEY_NUMBER(PMA, "page_size", page_size());
SCOPED_CRASH_KEY_BOOL(PMA, "is_full", IsFull());
SCOPED_CRASH_KEY_BOOL(PMA, "is_corrupted", IsCorrupt());
SCOPED_CRASH_KEY_NUMBER(PMA, "freeptr", freeptr());
SCOPED_CRASH_KEY_NUMBER(
PMA, "global_cookie",
static_cast<const volatile SharedMetadata*>(shared_meta())->cookie);
SCOPED_CRASH_KEY_NUMBER(PMA, "ref", ref);
SCOPED_CRASH_KEY_NUMBER(PMA, "expected_type", expected_type);
SCOPED_CRASH_KEY_NUMBER(PMA, "expected_size", expected_size);
const volatile BlockHeader* const block =
dump_block_header ? GetBlock(ref, expected_type,
expected_size, false,
false)
: nullptr;
std::string_view unknown = dump_block_header ? "unknown" : "N/A";
SCOPED_CRASH_KEY_STRING32(PMA, "block_size",
block ? NumberToString(block->size) : unknown);
SCOPED_CRASH_KEY_STRING32(PMA, "block_cookie",
block ? NumberToString(block->cookie) : unknown);
SCOPED_CRASH_KEY_STRING32(PMA, "block_type_id",
block ? NumberToString(block->type_id) : unknown);
SCOPED_CRASH_KEY_STRING32(PMA, "block_next",
block ? NumberToString(block->next) : unknown);
::base::debug::DumpWithoutCrashing();
}
LocalPersistentMemoryAllocator::LocalPersistentMemoryAllocator(
size_t size,
uint64_t id,
std::string_view name)
: PersistentMemoryAllocator(AllocateLocalMemory(size, name),
size,
0,
id,
name,
kReadWrite) {}
LocalPersistentMemoryAllocator::~LocalPersistentMemoryAllocator() {
DeallocateLocalMemory(const_cast<char*>(mem_base_), mem_size_, mem_type_);
}
PersistentMemoryAllocator::Memory
LocalPersistentMemoryAllocator::AllocateLocalMemory(size_t size,
std::string_view name) {
void* address;
#if BUILDFLAG(IS_WIN)
address =
::VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (address) {
return Memory(address, MEM_VIRTUAL);
}
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
address = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED,
-1, 0);
if (address != MAP_FAILED) {
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_ARKWEB)
const std::string arena_name = base::StrCat({"persistent:", name});
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, address, size, arena_name.c_str());
#endif
return Memory(address, MEM_VIRTUAL);
}
#else
#error This architecture is not (yet) supported.
#endif
address = malloc(size);
DPCHECK(address);
UNSAFE_TODO(memset(address, 0, size));
return Memory(address, MEM_MALLOC);
}
void LocalPersistentMemoryAllocator::DeallocateLocalMemory(void* memory,
size_t size,
MemoryType type) {
if (type == MEM_MALLOC) {
free(memory);
return;
}
DCHECK_EQ(MEM_VIRTUAL, type);
#if BUILDFLAG(IS_WIN)
BOOL success = ::VirtualFree(memory, 0, MEM_DECOMMIT);
DCHECK(success);
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
int result = ::munmap(memory, size);
DCHECK_EQ(0, result);
#else
#error This architecture is not (yet) supported.
#endif
}
WritableSharedPersistentMemoryAllocator::
WritableSharedPersistentMemoryAllocator(
base::WritableSharedMemoryMapping memory,
uint64_t id,
std::string_view name)
: PersistentMemoryAllocator(Memory(memory.memory(), MEM_SHARED),
memory.size(),
0,
id,
name,
kReadWrite),
shared_memory_(std::move(memory)) {}
WritableSharedPersistentMemoryAllocator::
~WritableSharedPersistentMemoryAllocator() = default;
bool WritableSharedPersistentMemoryAllocator::IsSharedMemoryAcceptable(
const base::WritableSharedMemoryMapping& memory) {
return IsMemoryAcceptable(memory.memory(), memory.size(), 0, false);
}
ReadOnlySharedPersistentMemoryAllocator::
ReadOnlySharedPersistentMemoryAllocator(
base::ReadOnlySharedMemoryMapping memory,
uint64_t id,
std::string_view name)
: PersistentMemoryAllocator(
Memory(const_cast<void*>(memory.memory()), MEM_SHARED),
memory.size(),
0,
id,
name,
kReadOnly),
shared_memory_(std::move(memory)) {}
ReadOnlySharedPersistentMemoryAllocator::
~ReadOnlySharedPersistentMemoryAllocator() = default;
bool ReadOnlySharedPersistentMemoryAllocator::IsSharedMemoryAcceptable(
const base::ReadOnlySharedMemoryMapping& memory) {
return IsMemoryAcceptable(memory.memory(), memory.size(), 0, true);
}
FilePersistentMemoryAllocator::FilePersistentMemoryAllocator(
std::unique_ptr<MemoryMappedFile> file,
size_t max_size,
uint64_t id,
std::string_view name,
AccessMode access_mode)
: PersistentMemoryAllocator(
Memory(const_cast<uint8_t*>(file->data()), MEM_FILE),
max_size != 0 ? max_size : file->length(),
0,
id,
name,
access_mode),
mapped_file_(std::move(file)) {}
FilePersistentMemoryAllocator::~FilePersistentMemoryAllocator() = default;
bool FilePersistentMemoryAllocator::IsFileAcceptable(
const MemoryMappedFile& file,
bool readonly) {
return IsMemoryAcceptable(file.data(), file.length(), 0, readonly);
}
void FilePersistentMemoryAllocator::Cache() {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
const volatile char* mem_end = UNSAFE_TODO(mem_base_ + used());
const volatile char* mem_begin = mem_base_;
int total = 0;
for (const volatile char* memory = mem_begin; memory < mem_end;
UNSAFE_TODO(memory += vm_page_size_)) {
total += *memory;
}
debug::Alias(&total);
}
void FilePersistentMemoryAllocator::FlushPartial(size_t length, bool sync) {
if (IsReadonly()) {
return;
}
std::optional<base::ScopedBlockingCall> scoped_blocking_call;
if (sync) {
scoped_blocking_call.emplace(FROM_HERE, base::BlockingType::MAY_BLOCK);
}
#if BUILDFLAG(IS_WIN)
scoped_blocking_call.emplace(FROM_HERE, base::BlockingType::MAY_BLOCK);
BOOL success = ::FlushViewOfFile(data(), length);
DPCHECK(success);
#elif BUILDFLAG(IS_APPLE)
int result =
::msync(const_cast<void*>(data()), length, sync ? MS_SYNC : MS_ASYNC);
DCHECK_NE(EINVAL, result);
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
int result = ::msync(const_cast<void*>(data()), length,
MS_INVALIDATE | (sync ? MS_SYNC : MS_ASYNC));
DCHECK_NE(EINVAL, result);
#else
#error Unsupported OS.
#endif
}
DelayedPersistentAllocation::DelayedPersistentAllocation(
PersistentMemoryAllocator* allocator,
std::atomic<Reference>* ref,
uint32_t type,
size_t size,
size_t offset)
: allocator_(allocator),
type_(type),
size_(checked_cast<uint32_t>(size)),
offset_(checked_cast<uint32_t>(offset)),
reference_(ref) {
DCHECK(allocator_);
DCHECK_NE(0U, type_);
DCHECK_LT(0U, size_);
DCHECK(reference_);
}
DelayedPersistentAllocation::~DelayedPersistentAllocation() = default;
span<uint8_t> DelayedPersistentAllocation::GetUntyped() const {
Reference ref = reference_->load(std::memory_order_acquire);
[[maybe_unused]] const bool ref_found = (ref != 0);
[[maybe_unused]] bool race_detected = false;
if (!ref) {
[[maybe_unused]] size_t alloc_size = 0;
ref = allocator_->Allocate(size_, type_, &alloc_size);
if (!ref) {
return span<uint8_t>();
}
Reference existing = 0;
if (!reference_->compare_exchange_strong(existing, ref,
std::memory_order_release,
std::memory_order_relaxed)) {
DCHECK_EQ(type_, allocator_->GetType(existing));
DCHECK_LE(size_, alloc_size);
allocator_->ChangeType(ref, 0, type_, false);
ref = existing;
race_detected = true;
}
}
uint8_t* mem = allocator_->GetAsArray<uint8_t>(ref, type_, size_);
if (mem) {
return UNSAFE_TODO(span(mem + offset_, size_ - offset_));
}
const bool ref_is_magic_number = (ref == kBlockCookieAllocated);
SCOPED_CRASH_KEY_STRING32(
PMA, "ref_value_before",
ref_is_magic_number
? NumberToString(
(UNSAFE_TODO(reference_ - 1))->load(std::memory_order_relaxed))
: "N/A");
SCOPED_CRASH_KEY_STRING32(
PMA, "ref_value_after",
ref_is_magic_number
? NumberToString(
(UNSAFE_TODO(reference_ + 1))->load(std::memory_order_relaxed))
: "N/A");
SCOPED_CRASH_KEY_BOOL(PMA, "ref_found", ref_found);
SCOPED_CRASH_KEY_BOOL(PMA, "race_detected", race_detected);
allocator_->DumpWithoutCrashing(ref,
type_,
size_,
true);
return span<uint8_t>();
}
}