1 // Copyright (c) 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/metrics/persistent_memory_allocator.h"
6
7 #include <assert.h>
8 #include <algorithm>
9
10 #if defined(OS_WIN)
11 #include "winbase.h"
12 #elif defined(OS_POSIX)
13 #include <sys/mman.h>
14 #endif
15
16 #include "base/files/memory_mapped_file.h"
17 #include "base/logging.h"
18 #include "base/memory/shared_memory.h"
19 #include "base/metrics/histogram_macros.h"
20
21 namespace {
22
23 // Limit of memory segment size. It has to fit in an unsigned 32-bit number
24 // and should be a power of 2 in order to accomodate almost any page size.
25 const uint32_t kSegmentMaxSize = 1 << 30; // 1 GiB
26
27 // A constant (random) value placed in the shared metadata to identify
28 // an already initialized memory segment.
29 const uint32_t kGlobalCookie = 0x408305DC;
30
31 // The current version of the metadata. If updates are made that change
32 // the metadata, the version number can be queried to operate in a backward-
33 // compatible manner until the memory segment is completely re-initalized.
34 const uint32_t kGlobalVersion = 1;
35
36 // Constant values placed in the block headers to indicate its state.
37 const uint32_t kBlockCookieFree = 0;
38 const uint32_t kBlockCookieQueue = 1;
39 const uint32_t kBlockCookieWasted = (uint32_t)-1;
40 const uint32_t kBlockCookieAllocated = 0xC8799269;
41
42 // TODO(bcwhite): When acceptable, consider moving flags to std::atomic<char>
43 // types rather than combined bitfield.
44
45 // Flags stored in the flags_ field of the SharedMetaData structure below.
46 enum : int {
47 kFlagCorrupt = 1 << 0,
48 kFlagFull = 1 << 1
49 };
50
CheckFlag(const volatile std::atomic<uint32_t> * flags,int flag)51 bool CheckFlag(const volatile std::atomic<uint32_t>* flags, int flag) {
52 uint32_t loaded_flags = flags->load(std::memory_order_relaxed);
53 return (loaded_flags & flag) != 0;
54 }
55
SetFlag(volatile std::atomic<uint32_t> * flags,int flag)56 void SetFlag(volatile std::atomic<uint32_t>* flags, int flag) {
57 uint32_t loaded_flags = flags->load(std::memory_order_relaxed);
58 for (;;) {
59 uint32_t new_flags = (loaded_flags & ~flag) | flag;
60 // In the failue case, actual "flags" value stored in loaded_flags.
61 if (flags->compare_exchange_weak(loaded_flags, new_flags))
62 break;
63 }
64 }
65
66 } // namespace
67
68 namespace base {
69
70 // All allocations and data-structures must be aligned to this byte boundary.
71 // Alignment as large as the physical bus between CPU and RAM is _required_
72 // for some architectures, is simply more efficient on other CPUs, and
73 // generally a Good Idea(tm) for all platforms as it reduces/eliminates the
74 // chance that a type will span cache lines. Alignment mustn't be less
75 // than 8 to ensure proper alignment for all types. The rest is a balance
76 // between reducing spans across multiple cache lines and wasted space spent
77 // padding out allocations. An alignment of 16 would ensure that the block
78 // header structure always sits in a single cache line. An average of about
79 // 1/2 this value will be wasted with every allocation.
80 const uint32_t PersistentMemoryAllocator::kAllocAlignment = 8;
81
82 // The block-header is placed at the top of every allocation within the
83 // segment to describe the data that follows it.
84 struct PersistentMemoryAllocator::BlockHeader {
85 uint32_t size; // Number of bytes in this block, including header.
86 uint32_t cookie; // Constant value indicating completed allocation.
87 std::atomic<uint32_t> type_id; // Arbitrary number indicating data type.
88 std::atomic<uint32_t> next; // Pointer to the next block when iterating.
89 };
90
91 // The shared metadata exists once at the top of the memory segment to
92 // describe the state of the allocator to all processes.
93 struct PersistentMemoryAllocator::SharedMetadata {
94 uint32_t cookie; // Some value that indicates complete initialization.
95 uint32_t size; // Total size of memory segment.
96 uint32_t page_size; // Paging size within memory segment.
97 uint32_t version; // Version code so upgrades don't break.
98 uint64_t id; // Arbitrary ID number given by creator.
99 uint32_t name; // Reference to stored name string.
100
101 // Above is read-only after first construction. Below may be changed and
102 // so must be marked "volatile" to provide correct inter-process behavior.
103
104 // Bitfield of information flags. Access to this should be done through
105 // the CheckFlag() and SetFlag() methods defined above.
106 volatile std::atomic<uint32_t> flags;
107
108 // Offset/reference to first free space in segment.
109 volatile std::atomic<uint32_t> freeptr;
110
111 // The "iterable" queue is an M&S Queue as described here, append-only:
112 // https://www.research.ibm.com/people/m/michael/podc-1996.pdf
113 volatile std::atomic<uint32_t> tailptr; // Last block of iteration queue.
114 volatile BlockHeader queue; // Empty block for linked-list head/tail.
115 };
116
117 // The "queue" block header is used to detect "last node" so that zero/null
118 // can be used to indicate that it hasn't been added at all. It is part of
119 // the SharedMetadata structure which itself is always located at offset zero.
120 const PersistentMemoryAllocator::Reference
121 PersistentMemoryAllocator::kReferenceQueue =
122 offsetof(SharedMetadata, queue);
123
124 const base::FilePath::CharType PersistentMemoryAllocator::kFileExtension[] =
125 FILE_PATH_LITERAL(".pma");
126
127
Iterator(const PersistentMemoryAllocator * allocator)128 PersistentMemoryAllocator::Iterator::Iterator(
129 const PersistentMemoryAllocator* allocator)
130 : allocator_(allocator), last_record_(kReferenceQueue), record_count_(0) {}
131
Iterator(const PersistentMemoryAllocator * allocator,Reference starting_after)132 PersistentMemoryAllocator::Iterator::Iterator(
133 const PersistentMemoryAllocator* allocator,
134 Reference starting_after)
135 : allocator_(allocator), last_record_(starting_after), record_count_(0) {
136 // Ensure that the starting point is a valid, iterable block (meaning it can
137 // be read and has a non-zero "next" pointer).
138 const volatile BlockHeader* block =
139 allocator_->GetBlock(starting_after, 0, 0, false, false);
140 if (!block || block->next.load(std::memory_order_relaxed) == 0) {
141 NOTREACHED();
142 last_record_.store(kReferenceQueue, std::memory_order_release);
143 }
144 }
145
146 PersistentMemoryAllocator::Reference
GetNext(uint32_t * type_return)147 PersistentMemoryAllocator::Iterator::GetNext(uint32_t* type_return) {
148 // Make a copy of the existing count of found-records, acquiring all changes
149 // made to the allocator, notably "freeptr" (see comment in loop for why
150 // the load of that value cannot be moved above here) that occurred during
151 // any previous runs of this method, including those by parallel threads
152 // that interrupted it. It pairs with the Release at the end of this method.
153 //
154 // Otherwise, if the compiler were to arrange the two loads such that
155 // "count" was fetched _after_ "freeptr" then it would be possible for
156 // this thread to be interrupted between them and other threads perform
157 // multiple allocations, make-iterables, and iterations (with the included
158 // increment of |record_count_|) culminating in the check at the bottom
159 // mistakenly determining that a loop exists. Isn't this stuff fun?
160 uint32_t count = record_count_.load(std::memory_order_acquire);
161
162 Reference last = last_record_.load(std::memory_order_acquire);
163 Reference next;
164 while (true) {
165 const volatile BlockHeader* block =
166 allocator_->GetBlock(last, 0, 0, true, false);
167 if (!block) // Invalid iterator state.
168 return kReferenceNull;
169
170 // The compiler and CPU can freely reorder all memory accesses on which
171 // there are no dependencies. It could, for example, move the load of
172 // "freeptr" to above this point because there are no explicit dependencies
173 // between it and "next". If it did, however, then another block could
174 // be queued after that but before the following load meaning there is
175 // one more queued block than the future "detect loop by having more
176 // blocks that could fit before freeptr" will allow.
177 //
178 // By "acquiring" the "next" value here, it's synchronized to the enqueue
179 // of the node which in turn is synchronized to the allocation (which sets
180 // freeptr). Thus, the scenario above cannot happen.
181 next = block->next.load(std::memory_order_acquire);
182 if (next == kReferenceQueue) // No next allocation in queue.
183 return kReferenceNull;
184 block = allocator_->GetBlock(next, 0, 0, false, false);
185 if (!block) { // Memory is corrupt.
186 allocator_->SetCorrupt();
187 return kReferenceNull;
188 }
189
190 // Update the "last_record" pointer to be the reference being returned.
191 // If it fails then another thread has already iterated past it so loop
192 // again. Failing will also load the existing value into "last" so there
193 // is no need to do another such load when the while-loop restarts. A
194 // "strong" compare-exchange is used because failing unnecessarily would
195 // mean repeating some fairly costly validations above.
196 if (last_record_.compare_exchange_strong(last, next)) {
197 *type_return = block->type_id.load(std::memory_order_relaxed);
198 break;
199 }
200 }
201
202 // Memory corruption could cause a loop in the list. Such must be detected
203 // so as to not cause an infinite loop in the caller. This is done by simply
204 // making sure it doesn't iterate more times than the absolute maximum
205 // number of allocations that could have been made. Callers are likely
206 // to loop multiple times before it is detected but at least it stops.
207 const uint32_t freeptr = std::min(
208 allocator_->shared_meta()->freeptr.load(std::memory_order_relaxed),
209 allocator_->mem_size_);
210 const uint32_t max_records =
211 freeptr / (sizeof(BlockHeader) + kAllocAlignment);
212 if (count > max_records) {
213 allocator_->SetCorrupt();
214 return kReferenceNull;
215 }
216
217 // Increment the count and release the changes made above. It pairs with
218 // the Acquire at the top of this method. Note that this operation is not
219 // strictly synchonized with fetching of the object to return, which would
220 // have to be done inside the loop and is somewhat complicated to achieve.
221 // It does not matter if it falls behind temporarily so long as it never
222 // gets ahead.
223 record_count_.fetch_add(1, std::memory_order_release);
224 return next;
225 }
226
227 PersistentMemoryAllocator::Reference
GetNextOfType(uint32_t type_match)228 PersistentMemoryAllocator::Iterator::GetNextOfType(uint32_t type_match) {
229 Reference ref;
230 uint32_t type_found;
231 while ((ref = GetNext(&type_found)) != 0) {
232 if (type_found == type_match)
233 return ref;
234 }
235 return kReferenceNull;
236 }
237
238
239 // static
IsMemoryAcceptable(const void * base,size_t size,size_t page_size,bool readonly)240 bool PersistentMemoryAllocator::IsMemoryAcceptable(const void* base,
241 size_t size,
242 size_t page_size,
243 bool readonly) {
244 return ((base && reinterpret_cast<uintptr_t>(base) % kAllocAlignment == 0) &&
245 (size >= sizeof(SharedMetadata) && size <= kSegmentMaxSize) &&
246 (size % kAllocAlignment == 0 || readonly) &&
247 (page_size == 0 || size % page_size == 0 || readonly));
248 }
249
PersistentMemoryAllocator(void * base,size_t size,size_t page_size,uint64_t id,base::StringPiece name,bool readonly)250 PersistentMemoryAllocator::PersistentMemoryAllocator(
251 void* base,
252 size_t size,
253 size_t page_size,
254 uint64_t id,
255 base::StringPiece name,
256 bool readonly)
257 : mem_base_(static_cast<char*>(base)),
258 mem_size_(static_cast<uint32_t>(size)),
259 mem_page_(static_cast<uint32_t>((page_size ? page_size : size))),
260 readonly_(readonly),
261 corrupt_(0),
262 allocs_histogram_(nullptr),
263 used_histogram_(nullptr) {
264 static_assert(sizeof(BlockHeader) % kAllocAlignment == 0,
265 "BlockHeader is not a multiple of kAllocAlignment");
266 static_assert(sizeof(SharedMetadata) % kAllocAlignment == 0,
267 "SharedMetadata is not a multiple of kAllocAlignment");
268 static_assert(kReferenceQueue % kAllocAlignment == 0,
269 "\"queue\" is not aligned properly; must be at end of struct");
270
271 // Ensure that memory segment is of acceptable size.
272 CHECK(IsMemoryAcceptable(base, size, page_size, readonly));
273
274 // These atomics operate inter-process and so must be lock-free. The local
275 // casts are to make sure it can be evaluated at compile time to a constant.
276 CHECK(((SharedMetadata*)0)->freeptr.is_lock_free());
277 CHECK(((SharedMetadata*)0)->flags.is_lock_free());
278 CHECK(((BlockHeader*)0)->next.is_lock_free());
279 CHECK(corrupt_.is_lock_free());
280
281 if (shared_meta()->cookie != kGlobalCookie) {
282 if (readonly) {
283 SetCorrupt();
284 return;
285 }
286
287 // This block is only executed when a completely new memory segment is
288 // being initialized. It's unshared and single-threaded...
289 volatile BlockHeader* const first_block =
290 reinterpret_cast<volatile BlockHeader*>(mem_base_ +
291 sizeof(SharedMetadata));
292 if (shared_meta()->cookie != 0 ||
293 shared_meta()->size != 0 ||
294 shared_meta()->version != 0 ||
295 shared_meta()->freeptr.load(std::memory_order_relaxed) != 0 ||
296 shared_meta()->flags.load(std::memory_order_relaxed) != 0 ||
297 shared_meta()->id != 0 ||
298 shared_meta()->name != 0 ||
299 shared_meta()->tailptr != 0 ||
300 shared_meta()->queue.cookie != 0 ||
301 shared_meta()->queue.next.load(std::memory_order_relaxed) != 0 ||
302 first_block->size != 0 ||
303 first_block->cookie != 0 ||
304 first_block->type_id.load(std::memory_order_relaxed) != 0 ||
305 first_block->next != 0) {
306 // ...or something malicious has been playing with the metadata.
307 SetCorrupt();
308 }
309
310 // This is still safe to do even if corruption has been detected.
311 shared_meta()->cookie = kGlobalCookie;
312 shared_meta()->size = mem_size_;
313 shared_meta()->page_size = mem_page_;
314 shared_meta()->version = kGlobalVersion;
315 shared_meta()->id = id;
316 shared_meta()->freeptr.store(sizeof(SharedMetadata),
317 std::memory_order_release);
318
319 // Set up the queue of iterable allocations.
320 shared_meta()->queue.size = sizeof(BlockHeader);
321 shared_meta()->queue.cookie = kBlockCookieQueue;
322 shared_meta()->queue.next.store(kReferenceQueue, std::memory_order_release);
323 shared_meta()->tailptr.store(kReferenceQueue, std::memory_order_release);
324
325 // Allocate space for the name so other processes can learn it.
326 if (!name.empty()) {
327 const size_t name_length = name.length() + 1;
328 shared_meta()->name = Allocate(name_length, 0);
329 char* name_cstr = GetAsObject<char>(shared_meta()->name, 0);
330 if (name_cstr)
331 memcpy(name_cstr, name.data(), name.length());
332 }
333 } else {
334 if (shared_meta()->size == 0 ||
335 shared_meta()->version == 0 ||
336 shared_meta()->freeptr.load(std::memory_order_relaxed) == 0 ||
337 shared_meta()->tailptr == 0 ||
338 shared_meta()->queue.cookie == 0 ||
339 shared_meta()->queue.next.load(std::memory_order_relaxed) == 0) {
340 SetCorrupt();
341 }
342 if (!readonly) {
343 // The allocator is attaching to a previously initialized segment of
344 // memory. If the initialization parameters differ, make the best of it
345 // by reducing the local construction parameters to match those of
346 // the actual memory area. This ensures that the local object never
347 // tries to write outside of the original bounds.
348 // Because the fields are const to ensure that no code other than the
349 // constructor makes changes to them as well as to give optimization
350 // hints to the compiler, it's necessary to const-cast them for changes
351 // here.
352 if (shared_meta()->size < mem_size_)
353 *const_cast<uint32_t*>(&mem_size_) = shared_meta()->size;
354 if (shared_meta()->page_size < mem_page_)
355 *const_cast<uint32_t*>(&mem_page_) = shared_meta()->page_size;
356
357 // Ensure that settings are still valid after the above adjustments.
358 if (!IsMemoryAcceptable(base, mem_size_, mem_page_, readonly))
359 SetCorrupt();
360 }
361 }
362 }
363
~PersistentMemoryAllocator()364 PersistentMemoryAllocator::~PersistentMemoryAllocator() {
365 // It's strictly forbidden to do any memory access here in case there is
366 // some issue with the underlying memory segment. The "Local" allocator
367 // makes use of this to allow deletion of the segment on the heap from
368 // within its destructor.
369 }
370
Id() const371 uint64_t PersistentMemoryAllocator::Id() const {
372 return shared_meta()->id;
373 }
374
Name() const375 const char* PersistentMemoryAllocator::Name() const {
376 Reference name_ref = shared_meta()->name;
377 const char* name_cstr = GetAsObject<char>(name_ref, 0);
378 if (!name_cstr)
379 return "";
380
381 size_t name_length = GetAllocSize(name_ref);
382 if (name_cstr[name_length - 1] != '\0') {
383 NOTREACHED();
384 SetCorrupt();
385 return "";
386 }
387
388 return name_cstr;
389 }
390
CreateTrackingHistograms(base::StringPiece name)391 void PersistentMemoryAllocator::CreateTrackingHistograms(
392 base::StringPiece name) {
393 if (name.empty() || readonly_)
394 return;
395
396 std::string name_string = name.as_string();
397 DCHECK(!used_histogram_);
398 used_histogram_ = LinearHistogram::FactoryGet(
399 "UMA.PersistentAllocator." + name_string + ".UsedPct", 1, 101, 21,
400 HistogramBase::kUmaTargetedHistogramFlag);
401
402 DCHECK(!allocs_histogram_);
403 allocs_histogram_ = Histogram::FactoryGet(
404 "UMA.PersistentAllocator." + name_string + ".Allocs", 1, 10000, 50,
405 HistogramBase::kUmaTargetedHistogramFlag);
406 }
407
used() const408 size_t PersistentMemoryAllocator::used() const {
409 return std::min(shared_meta()->freeptr.load(std::memory_order_relaxed),
410 mem_size_);
411 }
412
GetAllocSize(Reference ref) const413 size_t PersistentMemoryAllocator::GetAllocSize(Reference ref) const {
414 const volatile BlockHeader* const block = GetBlock(ref, 0, 0, false, false);
415 if (!block)
416 return 0;
417 uint32_t size = block->size;
418 // Header was verified by GetBlock() but a malicious actor could change
419 // the value between there and here. Check it again.
420 if (size <= sizeof(BlockHeader) || ref + size > mem_size_) {
421 SetCorrupt();
422 return 0;
423 }
424 return size - sizeof(BlockHeader);
425 }
426
GetType(Reference ref) const427 uint32_t PersistentMemoryAllocator::GetType(Reference ref) const {
428 const volatile BlockHeader* const block = GetBlock(ref, 0, 0, false, false);
429 if (!block)
430 return 0;
431 return block->type_id.load(std::memory_order_relaxed);
432 }
433
ChangeType(Reference ref,uint32_t to_type_id,uint32_t from_type_id)434 bool PersistentMemoryAllocator::ChangeType(Reference ref,
435 uint32_t to_type_id,
436 uint32_t from_type_id) {
437 DCHECK(!readonly_);
438 volatile BlockHeader* const block = GetBlock(ref, 0, 0, false, false);
439 if (!block)
440 return false;
441
442 // This is a "strong" exchange because there is no loop that can retry in
443 // the wake of spurious failures possible with "weak" exchanges.
444 return block->type_id.compare_exchange_strong(from_type_id, to_type_id);
445 }
446
Allocate(size_t req_size,uint32_t type_id)447 PersistentMemoryAllocator::Reference PersistentMemoryAllocator::Allocate(
448 size_t req_size,
449 uint32_t type_id) {
450 Reference ref = AllocateImpl(req_size, type_id);
451 if (ref) {
452 // Success: Record this allocation in usage stats (if active).
453 if (allocs_histogram_)
454 allocs_histogram_->Add(static_cast<HistogramBase::Sample>(req_size));
455 } else {
456 // Failure: Record an allocation of zero for tracking.
457 if (allocs_histogram_)
458 allocs_histogram_->Add(0);
459 }
460 return ref;
461 }
462
AllocateImpl(size_t req_size,uint32_t type_id)463 PersistentMemoryAllocator::Reference PersistentMemoryAllocator::AllocateImpl(
464 size_t req_size,
465 uint32_t type_id) {
466 DCHECK(!readonly_);
467
468 // Validate req_size to ensure it won't overflow when used as 32-bit value.
469 if (req_size > kSegmentMaxSize - sizeof(BlockHeader)) {
470 NOTREACHED();
471 return kReferenceNull;
472 }
473
474 // Round up the requested size, plus header, to the next allocation alignment.
475 uint32_t size = static_cast<uint32_t>(req_size + sizeof(BlockHeader));
476 size = (size + (kAllocAlignment - 1)) & ~(kAllocAlignment - 1);
477 if (size <= sizeof(BlockHeader) || size > mem_page_) {
478 NOTREACHED();
479 return kReferenceNull;
480 }
481
482 // Get the current start of unallocated memory. Other threads may
483 // update this at any time and cause us to retry these operations.
484 // This value should be treated as "const" to avoid confusion through
485 // the code below but recognize that any failed compare-exchange operation
486 // involving it will cause it to be loaded with a more recent value. The
487 // code should either exit or restart the loop in that case.
488 /* const */ uint32_t freeptr =
489 shared_meta()->freeptr.load(std::memory_order_acquire);
490
491 // Allocation is lockless so we do all our caculation and then, if saving
492 // indicates a change has occurred since we started, scrap everything and
493 // start over.
494 for (;;) {
495 if (IsCorrupt())
496 return kReferenceNull;
497
498 if (freeptr + size > mem_size_) {
499 SetFlag(&shared_meta()->flags, kFlagFull);
500 return kReferenceNull;
501 }
502
503 // Get pointer to the "free" block. If something has been allocated since
504 // the load of freeptr above, it is still safe as nothing will be written
505 // to that location until after the compare-exchange below.
506 volatile BlockHeader* const block = GetBlock(freeptr, 0, 0, false, true);
507 if (!block) {
508 SetCorrupt();
509 return kReferenceNull;
510 }
511
512 // An allocation cannot cross page boundaries. If it would, create a
513 // "wasted" block and begin again at the top of the next page. This
514 // area could just be left empty but we fill in the block header just
515 // for completeness sake.
516 const uint32_t page_free = mem_page_ - freeptr % mem_page_;
517 if (size > page_free) {
518 if (page_free <= sizeof(BlockHeader)) {
519 SetCorrupt();
520 return kReferenceNull;
521 }
522 const uint32_t new_freeptr = freeptr + page_free;
523 if (shared_meta()->freeptr.compare_exchange_strong(freeptr,
524 new_freeptr)) {
525 block->size = page_free;
526 block->cookie = kBlockCookieWasted;
527 }
528 continue;
529 }
530
531 // Don't leave a slice at the end of a page too small for anything. This
532 // can result in an allocation up to two alignment-sizes greater than the
533 // minimum required by requested-size + header + alignment.
534 if (page_free - size < sizeof(BlockHeader) + kAllocAlignment)
535 size = page_free;
536
537 const uint32_t new_freeptr = freeptr + size;
538 if (new_freeptr > mem_size_) {
539 SetCorrupt();
540 return kReferenceNull;
541 }
542
543 // Save our work. Try again if another thread has completed an allocation
544 // while we were processing. A "weak" exchange would be permissable here
545 // because the code will just loop and try again but the above processing
546 // is significant so make the extra effort of a "strong" exchange.
547 if (!shared_meta()->freeptr.compare_exchange_strong(freeptr, new_freeptr))
548 continue;
549
550 // Given that all memory was zeroed before ever being given to an instance
551 // of this class and given that we only allocate in a monotomic fashion
552 // going forward, it must be that the newly allocated block is completely
553 // full of zeros. If we find anything in the block header that is NOT a
554 // zero then something must have previously run amuck through memory,
555 // writing beyond the allocated space and into unallocated space.
556 if (block->size != 0 ||
557 block->cookie != kBlockCookieFree ||
558 block->type_id.load(std::memory_order_relaxed) != 0 ||
559 block->next.load(std::memory_order_relaxed) != 0) {
560 SetCorrupt();
561 return kReferenceNull;
562 }
563
564 block->size = size;
565 block->cookie = kBlockCookieAllocated;
566 block->type_id.store(type_id, std::memory_order_relaxed);
567 return freeptr;
568 }
569 }
570
GetMemoryInfo(MemoryInfo * meminfo) const571 void PersistentMemoryAllocator::GetMemoryInfo(MemoryInfo* meminfo) const {
572 uint32_t remaining = std::max(
573 mem_size_ - shared_meta()->freeptr.load(std::memory_order_relaxed),
574 (uint32_t)sizeof(BlockHeader));
575 meminfo->total = mem_size_;
576 meminfo->free = IsCorrupt() ? 0 : remaining - sizeof(BlockHeader);
577 }
578
MakeIterable(Reference ref)579 void PersistentMemoryAllocator::MakeIterable(Reference ref) {
580 DCHECK(!readonly_);
581 if (IsCorrupt())
582 return;
583 volatile BlockHeader* block = GetBlock(ref, 0, 0, false, false);
584 if (!block) // invalid reference
585 return;
586 if (block->next.load(std::memory_order_acquire) != 0) // Already iterable.
587 return;
588 block->next.store(kReferenceQueue, std::memory_order_release); // New tail.
589
590 // Try to add this block to the tail of the queue. May take multiple tries.
591 // If so, tail will be automatically updated with a more recent value during
592 // compare-exchange operations.
593 uint32_t tail = shared_meta()->tailptr.load(std::memory_order_acquire);
594 for (;;) {
595 // Acquire the current tail-pointer released by previous call to this
596 // method and validate it.
597 block = GetBlock(tail, 0, 0, true, false);
598 if (!block) {
599 SetCorrupt();
600 return;
601 }
602
603 // Try to insert the block at the tail of the queue. The tail node always
604 // has an existing value of kReferenceQueue; if that is somehow not the
605 // existing value then another thread has acted in the meantime. A "strong"
606 // exchange is necessary so the "else" block does not get executed when
607 // that is not actually the case (which can happen with a "weak" exchange).
608 uint32_t next = kReferenceQueue; // Will get replaced with existing value.
609 if (block->next.compare_exchange_strong(next, ref,
610 std::memory_order_acq_rel,
611 std::memory_order_acquire)) {
612 // Update the tail pointer to the new offset. If the "else" clause did
613 // not exist, then this could be a simple Release_Store to set the new
614 // value but because it does, it's possible that other threads could add
615 // one or more nodes at the tail before reaching this point. We don't
616 // have to check the return value because it either operates correctly
617 // or the exact same operation has already been done (by the "else"
618 // clause) on some other thread.
619 shared_meta()->tailptr.compare_exchange_strong(tail, ref,
620 std::memory_order_release,
621 std::memory_order_relaxed);
622 return;
623 } else {
624 // In the unlikely case that a thread crashed or was killed between the
625 // update of "next" and the update of "tailptr", it is necessary to
626 // perform the operation that would have been done. There's no explicit
627 // check for crash/kill which means that this operation may also happen
628 // even when the other thread is in perfect working order which is what
629 // necessitates the CompareAndSwap above.
630 shared_meta()->tailptr.compare_exchange_strong(tail, next,
631 std::memory_order_acq_rel,
632 std::memory_order_acquire);
633 }
634 }
635 }
636
637 // The "corrupted" state is held both locally and globally (shared). The
638 // shared flag can't be trusted since a malicious actor could overwrite it.
639 // Because corruption can be detected during read-only operations such as
640 // iteration, this method may be called by other "const" methods. In this
641 // case, it's safe to discard the constness and modify the local flag and
642 // maybe even the shared flag if the underlying data isn't actually read-only.
SetCorrupt() const643 void PersistentMemoryAllocator::SetCorrupt() const {
644 LOG(ERROR) << "Corruption detected in shared-memory segment.";
645 const_cast<std::atomic<bool>*>(&corrupt_)->store(true,
646 std::memory_order_relaxed);
647 if (!readonly_) {
648 SetFlag(const_cast<volatile std::atomic<uint32_t>*>(&shared_meta()->flags),
649 kFlagCorrupt);
650 }
651 }
652
IsCorrupt() const653 bool PersistentMemoryAllocator::IsCorrupt() const {
654 if (corrupt_.load(std::memory_order_relaxed) ||
655 CheckFlag(&shared_meta()->flags, kFlagCorrupt)) {
656 SetCorrupt(); // Make sure all indicators are set.
657 return true;
658 }
659 return false;
660 }
661
IsFull() const662 bool PersistentMemoryAllocator::IsFull() const {
663 return CheckFlag(&shared_meta()->flags, kFlagFull);
664 }
665
666 // Dereference a block |ref| and ensure that it's valid for the desired
667 // |type_id| and |size|. |special| indicates that we may try to access block
668 // headers not available to callers but still accessed by this module. By
669 // having internal dereferences go through this same function, the allocator
670 // is hardened against corruption.
671 const volatile PersistentMemoryAllocator::BlockHeader*
GetBlock(Reference ref,uint32_t type_id,uint32_t size,bool queue_ok,bool free_ok) const672 PersistentMemoryAllocator::GetBlock(Reference ref, uint32_t type_id,
673 uint32_t size, bool queue_ok,
674 bool free_ok) const {
675 // Validation of parameters.
676 if (ref % kAllocAlignment != 0)
677 return nullptr;
678 if (ref < (queue_ok ? kReferenceQueue : sizeof(SharedMetadata)))
679 return nullptr;
680 size += sizeof(BlockHeader);
681 if (ref + size > mem_size_)
682 return nullptr;
683
684 // Validation of referenced block-header.
685 if (!free_ok) {
686 uint32_t freeptr = std::min(
687 shared_meta()->freeptr.load(std::memory_order_relaxed), mem_size_);
688 if (ref + size > freeptr)
689 return nullptr;
690 const volatile BlockHeader* const block =
691 reinterpret_cast<volatile BlockHeader*>(mem_base_ + ref);
692 if (block->size < size)
693 return nullptr;
694 if (ref + block->size > freeptr)
695 return nullptr;
696 if (ref != kReferenceQueue && block->cookie != kBlockCookieAllocated)
697 return nullptr;
698 if (type_id != 0 &&
699 block->type_id.load(std::memory_order_relaxed) != type_id) {
700 return nullptr;
701 }
702 }
703
704 // Return pointer to block data.
705 return reinterpret_cast<const volatile BlockHeader*>(mem_base_ + ref);
706 }
707
GetBlockData(Reference ref,uint32_t type_id,uint32_t size) const708 const volatile void* PersistentMemoryAllocator::GetBlockData(
709 Reference ref,
710 uint32_t type_id,
711 uint32_t size) const {
712 DCHECK(size > 0);
713 const volatile BlockHeader* block =
714 GetBlock(ref, type_id, size, false, false);
715 if (!block)
716 return nullptr;
717 return reinterpret_cast<const volatile char*>(block) + sizeof(BlockHeader);
718 }
719
UpdateTrackingHistograms()720 void PersistentMemoryAllocator::UpdateTrackingHistograms() {
721 DCHECK(!readonly_);
722 if (used_histogram_) {
723 MemoryInfo meminfo;
724 GetMemoryInfo(&meminfo);
725 HistogramBase::Sample used_percent = static_cast<HistogramBase::Sample>(
726 ((meminfo.total - meminfo.free) * 100ULL / meminfo.total));
727 used_histogram_->Add(used_percent);
728 }
729 }
730
731
732 //----- LocalPersistentMemoryAllocator -----------------------------------------
733
LocalPersistentMemoryAllocator(size_t size,uint64_t id,base::StringPiece name)734 LocalPersistentMemoryAllocator::LocalPersistentMemoryAllocator(
735 size_t size,
736 uint64_t id,
737 base::StringPiece name)
738 : PersistentMemoryAllocator(AllocateLocalMemory(size),
739 size, 0, id, name, false) {}
740
~LocalPersistentMemoryAllocator()741 LocalPersistentMemoryAllocator::~LocalPersistentMemoryAllocator() {
742 DeallocateLocalMemory(const_cast<char*>(mem_base_), mem_size_);
743 }
744
745 // static
AllocateLocalMemory(size_t size)746 void* LocalPersistentMemoryAllocator::AllocateLocalMemory(size_t size) {
747 #if defined(OS_WIN)
748 void* address =
749 ::VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
750 DPCHECK(address);
751 return address;
752 #elif defined(OS_POSIX)
753 // MAP_ANON is deprecated on Linux but MAP_ANONYMOUS is not universal on Mac.
754 // MAP_SHARED is not available on Linux <2.4 but required on Mac.
755 void* address = ::mmap(nullptr, size, PROT_READ | PROT_WRITE,
756 MAP_ANON | MAP_SHARED, -1, 0);
757 DPCHECK(MAP_FAILED != address);
758 return address;
759 #else
760 #error This architecture is not (yet) supported.
761 #endif
762 }
763
764 // static
DeallocateLocalMemory(void * memory,size_t size)765 void LocalPersistentMemoryAllocator::DeallocateLocalMemory(void* memory,
766 size_t size) {
767 #if defined(OS_WIN)
768 BOOL success = ::VirtualFree(memory, 0, MEM_DECOMMIT);
769 DPCHECK(success);
770 #elif defined(OS_POSIX)
771 int result = ::munmap(memory, size);
772 DPCHECK(0 == result);
773 #else
774 #error This architecture is not (yet) supported.
775 #endif
776 }
777
778
779 //----- SharedPersistentMemoryAllocator ----------------------------------------
780
SharedPersistentMemoryAllocator(std::unique_ptr<SharedMemory> memory,uint64_t id,base::StringPiece name,bool read_only)781 SharedPersistentMemoryAllocator::SharedPersistentMemoryAllocator(
782 std::unique_ptr<SharedMemory> memory,
783 uint64_t id,
784 base::StringPiece name,
785 bool read_only)
786 : PersistentMemoryAllocator(static_cast<uint8_t*>(memory->memory()),
787 memory->mapped_size(),
788 0,
789 id,
790 name,
791 read_only),
792 shared_memory_(std::move(memory)) {}
793
~SharedPersistentMemoryAllocator()794 SharedPersistentMemoryAllocator::~SharedPersistentMemoryAllocator() {}
795
796 // static
IsSharedMemoryAcceptable(const SharedMemory & memory)797 bool SharedPersistentMemoryAllocator::IsSharedMemoryAcceptable(
798 const SharedMemory& memory) {
799 return IsMemoryAcceptable(memory.memory(), memory.mapped_size(), 0, false);
800 }
801
802
803 #if !defined(OS_NACL)
804 //----- FilePersistentMemoryAllocator ------------------------------------------
805
FilePersistentMemoryAllocator(std::unique_ptr<MemoryMappedFile> file,size_t max_size,uint64_t id,base::StringPiece name,bool read_only)806 FilePersistentMemoryAllocator::FilePersistentMemoryAllocator(
807 std::unique_ptr<MemoryMappedFile> file,
808 size_t max_size,
809 uint64_t id,
810 base::StringPiece name,
811 bool read_only)
812 : PersistentMemoryAllocator(const_cast<uint8_t*>(file->data()),
813 max_size != 0 ? max_size : file->length(),
814 0,
815 id,
816 name,
817 read_only),
818 mapped_file_(std::move(file)) {}
819
~FilePersistentMemoryAllocator()820 FilePersistentMemoryAllocator::~FilePersistentMemoryAllocator() {}
821
822 // static
IsFileAcceptable(const MemoryMappedFile & file,bool read_only)823 bool FilePersistentMemoryAllocator::IsFileAcceptable(
824 const MemoryMappedFile& file,
825 bool read_only) {
826 return IsMemoryAcceptable(file.data(), file.length(), 0, read_only);
827 }
828 #endif // !defined(OS_NACL)
829
830 } // namespace base
831