blob: e024a3ca5ae49585b317fa28563b77c53412513b [file] [log] [blame]
/*
* Copyright (c) 2023-2025 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "TestHarness.h"
#include "pas_root.h"
#include "bmalloc_heap.h"
#include "pas_probabilistic_guard_malloc_allocator.h"
#include "pas_heap.h"
#include "iso_heap.h"
#include "iso_heap_config.h"
#include "pas_heap_ref_kind.h"
#include <map>
#include <stdlib.h>
#include <set>
#include <sys/mman.h>
using namespace std;
namespace {
const bool verbose = false;
struct PageRange {
PageRange() = default;
PageRange(void* base, size_t size)
: base(base)
, size(size)
{
PAS_ASSERT(pas_is_aligned(reinterpret_cast<uintptr_t>(base), pas_page_malloc_alignment()));
PAS_ASSERT(pas_is_aligned(size, pas_page_malloc_alignment()));
PAS_ASSERT(!!base == !!size);
}
bool operator<(PageRange range) const
{
return base < range.base;
}
void* end() const
{
return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(base) + size);
}
void* base { nullptr };
size_t size { 0 };
};
set<PageRange> pageRanges;
struct RecordedRange {
RecordedRange() = default;
RecordedRange(void* base, size_t size)
: base(base)
, size(size)
{
PAS_ASSERT(base);
}
bool operator<(RecordedRange other) const
{
return base < other.base;
}
void* end() const
{
return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(base) + size);
}
void* base { nullptr };
size_t size { 0 };
};
struct ReaderRange {
ReaderRange() = default;
ReaderRange(void* base, size_t size)
: base(base)
, size(size)
{
PAS_ASSERT(base);
PAS_ASSERT(size);
}
void* base { nullptr };
size_t size { 0 };
};
map<pas_enumerator_record_kind, set<RecordedRange>> recordedRanges;
ReaderRange readerPreviousBuffer;
void* enumeratorReader(pas_enumerator* enumerator, void* address, size_t size, void* arg)
{
CHECK(!arg);
CHECK(size);
// Scribble the previously returned buffer to simulate the previously mapped region
// being invalidated as per the specification of memory_reader_t in malloc.h:
//
// typedef kern_return_t memory_reader_t(task_t remote_task, vm_address_t remote_address, vm_size_t size, void * __sized_by(size) *local_memory);
//
// given a task, "reads" the memory at the given address and size
// local_memory: set to a contiguous chunk of memory; validity of local_memory is assumed to be limited (until next call)
//
if (readerPreviousBuffer.base && readerPreviousBuffer.size)
memset(readerPreviousBuffer.base, 0xda, readerPreviousBuffer.size);
void* result = pas_enumerator_allocate(enumerator, size);
void* pageAddress = reinterpret_cast<void*>(
pas_round_down_to_power_of_2(
reinterpret_cast<uintptr_t>(address),
pas_page_malloc_alignment()));
size_t pagesSize =
pas_round_up_to_power_of_2(
reinterpret_cast<uintptr_t>(address) + size,
pas_page_malloc_alignment())
- reinterpret_cast<uintptr_t>(pageAddress);
void* pageEndAddress = reinterpret_cast<void*>(
reinterpret_cast<uintptr_t>(pageAddress) + pagesSize);
bool areProtectedPages = false;
if (!pageRanges.empty()) {
auto pageRangeIter = pageRanges.upper_bound(PageRange(pageAddress, pagesSize));
if (pageRangeIter != pageRanges.begin()) {
--pageRangeIter;
areProtectedPages =
pageRangeIter->base <= pageAddress
&& pageRangeIter->end() > pageAddress;
}
}
if (verbose) {
cout << "address = " << address << "..."
<< reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(address) + size)
<< ", pageAddress = " << pageAddress << "..." << pageEndAddress
<< ", areProtectedPages = " << areProtectedPages << "\n";
}
if (areProtectedPages) {
int systemResult = mprotect(pageAddress, pagesSize, PROT_READ);
PAS_ASSERT(!systemResult);
}
memcpy(result, address, size);
if (areProtectedPages) {
int systemResult = mprotect(pageAddress, pagesSize, PROT_NONE);
PAS_ASSERT(!systemResult);
}
readerPreviousBuffer = ReaderRange(result, size);
return result;
}
void enumeratorRecorder(pas_enumerator* enumerator, void* address, size_t size, pas_enumerator_record_kind kind, void* arg)
{
PAS_UNUSED_PARAM(enumerator);
CHECK(size);
CHECK(!arg);
if (verbose) {
cout << "Recording " << pas_enumerator_record_kind_get_string(kind) << ":" << address
<< "..." << reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(address) + size) << "\n";
}
RecordedRange range = RecordedRange(address, size);
CHECK(!recordedRanges[kind].count(range));
recordedRanges[kind].insert(range);
}
void testBasicEnumeration() {
pas_heap_lock_lock();
pas_root* root = pas_root_create();
pas_heap_lock_unlock();
auto size = 25;
void* arr[size];
for (auto i = 0; i < size; i++) {
arr[i] = bmalloc_try_allocate(1000000, pas_non_compact_allocation_mode);
PAS_ASSERT(arr[i]);
}
pas_enumerator* enumerator = pas_enumerator_create(root, enumeratorReader, nullptr, enumeratorRecorder, nullptr, pas_enumerator_record_meta_records, pas_enumerator_record_payload_records, pas_enumerator_record_object_records);
pas_enumerator_enumerate_all(enumerator);
pas_enumerator_destroy(enumerator);
}
void testPGMEnumerationBasic() {
pas_heap_lock_lock();
pas_root* root = pas_root_create();
pas_heap_lock_unlock();
pas_heap_ref heapRef = ISO_HEAP_REF_INITIALIZER_WITH_ALIGNMENT(getpagesize() * 100, getpagesize());
pas_heap* heap = iso_heap_ref_get_heap(&heapRef);
pas_physical_memory_transaction transaction;
pas_physical_memory_transaction_construct(&transaction);
pas_heap_lock_lock();
size_t alloc_size = 16384;
pas_allocation_result result = pas_probabilistic_guard_malloc_allocate(&heap->large_heap, alloc_size, 1, pas_non_compact_allocation_mode, &iso_heap_config, &transaction);
CHECK(result.begin);
result = pas_probabilistic_guard_malloc_allocate(&heap->large_heap, alloc_size, 1, pas_non_compact_allocation_mode, &iso_heap_config, &transaction);
CHECK(result.begin);
result = pas_probabilistic_guard_malloc_allocate(&heap->large_heap, alloc_size, 1, pas_non_compact_allocation_mode, &iso_heap_config, &transaction);
CHECK(result.begin);
pas_heap_lock_unlock();
pas_enumerator* enumerator = pas_enumerator_create(root, enumeratorReader, nullptr, enumeratorRecorder, nullptr, pas_enumerator_record_meta_records, pas_enumerator_record_payload_records, pas_enumerator_record_object_records);
pas_enumerator_enumerate_all(enumerator);
CHECK(enumerator);
pas_enumerator_destroy(enumerator);
}
void testPGMEnumerationAddAndFree() {
pas_heap_lock_lock();
pas_root* root = pas_root_create();
pas_heap_lock_unlock();
pas_heap_ref heapRef = ISO_HEAP_REF_INITIALIZER_WITH_ALIGNMENT(getpagesize() * 100, getpagesize());
pas_heap* heap = iso_heap_ref_get_heap(&heapRef);
pas_physical_memory_transaction transaction;
pas_physical_memory_transaction_construct(&transaction);
pas_heap_lock_lock();
size_t alloc_size = 16384;
pas_allocation_result result = pas_probabilistic_guard_malloc_allocate(&heap->large_heap, alloc_size, 1, pas_non_compact_allocation_mode, &iso_heap_config, &transaction);
CHECK(result.begin);
result = pas_probabilistic_guard_malloc_allocate(&heap->large_heap, alloc_size, 1, pas_non_compact_allocation_mode, &iso_heap_config, &transaction);
CHECK(result.begin);
result = pas_probabilistic_guard_malloc_allocate(&heap->large_heap, alloc_size, 1, pas_non_compact_allocation_mode, &iso_heap_config, &transaction);
CHECK(result.begin);
pas_probabilistic_guard_malloc_deallocate((void*) result.begin);
pas_heap_lock_unlock();
pas_enumerator* enumerator = pas_enumerator_create(root, enumeratorReader, nullptr, enumeratorRecorder, nullptr, pas_enumerator_record_meta_records, pas_enumerator_record_payload_records, pas_enumerator_record_object_records);
pas_enumerator_enumerate_all(enumerator);
CHECK(enumerator);
pas_enumerator_destroy(enumerator);
}
} // end namespace
void addEnumerationTests() {
ADD_TEST(testBasicEnumeration());
ADD_TEST(testPGMEnumerationBasic());
ADD_TEST(testPGMEnumerationAddAndFree());
}