#include"allocator.h" #include"android-base/stringprintf.h" #include"android-base/unique_fd.h" #include"bit_utils.h" #include"globals.h" #include"logging.h"// For VLOG_IS_ON. #include"memory_tool.h" #include"mman.h"// For the PROT_* and MAP_* constants. #include"utils.h"
using android::base::StringPrintf; using android::base::unique_fd;
template<class Key, class T, AllocatorTag kTag, class Compare = std::less<Key>> using AllocationTrackingMultiMap =
std::multimap<Key, T, Compare, TrackingAllocator<std::pair<const Key, T>, kTag>>;
using Maps = AllocationTrackingMultiMap<void*, MemMap*, kAllocatorTagMaps>;
// All the non-empty MemMaps. Use a multimap as we do a reserve-and-divide (eg ElfMap::Load()). static Maps* gMaps GUARDED_BY(MemMap::GetMemMapsLock()) = nullptr;
// A map containing unique strings used for indentifying anonymous mappings static std::map<std::string, int> debugStrMap GUARDED_BY(MemMap::GetMemMapsLock());
// Retrieve iterator to a `gMaps` entry that is known to exist.
Maps::iterator GetGMapsEntry(const MemMap& map) REQUIRES(MemMap::GetMemMapsLock()) {
DCHECK(map.IsValid());
DCHECK(gMaps != nullptr); for (auto it = gMaps->lower_bound(map.BaseBegin()), end = gMaps->end();
it != end && it->first == map.BaseBegin();
++it) { if (it->second == &map) { return it;
}
}
LOG(FATAL) << "MemMap not found";
UNREACHABLE();
}
std::ostream& operator<<(std::ostream& os, const Maps& mem_maps) {
os << "MemMap:" << std::endl; for (auto it = mem_maps.begin(); it != mem_maps.end(); ++it) { void* base = it->first;
MemMap* map = it->second;
CHECK_EQ(base, map->BaseBegin());
os << *map << std::endl;
} return os;
}
#if USE_ART_LOW_4G_ALLOCATOR // Handling mem_map in 32b address range for 64b architectures that do not support MAP_32BIT.
// The regular start of memory allocations. The first 64KB is protected by SELinux. static constexpr uintptr_t LOW_MEM_START = 64 * KB;
// Generate random starting position. // To not interfere with image position, take the image's address and only place it below. Current // formula (sketch): // // ART_BASE_ADDR = 0001XXXXXXXXXXXXXXX // ---------------------------------------- // = 0000111111111111111 // & ~(page_size - 1) =~0000000000000001111 // ---------------------------------------- // mask = 0000111111111110000 // & random data = YYYYYYYYYYYYYYYYYYY // ----------------------------------- // tmp = 0000YYYYYYYYYYY0000 // + LOW_MEM_START = 0000000000001000000 // -------------------------------------- // start // // arc4random as an entropy source is exposed in Bionic, but not in glibc. When we // do not have Bionic, simply start with LOW_MEM_START.
// Function is standalone so it can be tested somewhat in mem_map_test.cc. #ifdef __BIONIC__
uintptr_t CreateStartPos(uint64_t input, size_t page_size) {
CHECK_NE(0, ART_BASE_ADDRESS);
// Start with all bits below highest bit in ART_BASE_ADDRESS.
constexpr size_t leading_zeros = CLZ(static_cast<uint32_t>(ART_BASE_ADDRESS));
constexpr uintptr_t mask_ones = (1 << (31 - leading_zeros)) - 1;
// Lowest (usually 12) bits are not used, as aligned by page size. const uintptr_t mask = mask_ones & ~(page_size - 1);
static uintptr_t GenerateNextMemPos(size_t page_size) { #ifdef __BIONIC__
uint64_t random_data;
arc4random_buf(&random_data, sizeof(random_data)); return CreateStartPos(random_data, page_size); #else
UNUSED(page_size); // No arc4random on host, see above. return LOW_MEM_START; #endif
}
uintptr_t MemMap::next_mem_pos_; #endif
// Return true if the address range is contained in a single memory map by either reading // the gMaps variable or the /proc/self/map entry. bool MemMap::ContainedWithinExistingMap(uint8_t* ptr, size_t size, std::string* error_msg) {
uintptr_t begin = reinterpret_cast<uintptr_t>(ptr);
uintptr_t end = begin + size;
{
std::lock_guard<std::mutex> mu(*mem_maps_lock_); for (auto& pair : *gMaps) {
MemMap* const map = pair.second; if (begin >= reinterpret_cast<uintptr_t>(map->Begin()) &&
end <= reinterpret_cast<uintptr_t>(map->End())) { returntrue;
}
}
}
if (error_msg != nullptr) {
PrintFileToLog("/proc/self/maps", LogSeverity::ERROR);
*error_msg = StringPrintf("Requested region 0x%08" PRIxPTR "-0x%08" PRIxPTR " does not overlap " "any existing map. See process maps in the log.", begin, end);
} returnfalse;
}
// CheckMapRequest to validate a non-MAP_FAILED mmap result based on // the expected value, calling munmap if validation fails, giving the // reason in error_msg. // // If the expected_ptr is null, nothing is checked beyond the fact // that the actual_ptr is not MAP_FAILED. However, if expected_ptr is // non-null, we check that pointer is the actual_ptr == expected_ptr, // and if not, report in error_msg what the conflict mapping was if // found, or a generic error in other cases. bool MemMap::CheckMapRequest(uint8_t* expected_ptr, void* actual_ptr, size_t byte_count,
std::string* error_msg) { // Handled first by caller for more specific error messages.
CHECK(actual_ptr != MAP_FAILED);
if (expected_ptr == nullptr) { returntrue;
}
uintptr_t actual = reinterpret_cast<uintptr_t>(actual_ptr);
uintptr_t expected = reinterpret_cast<uintptr_t>(expected_ptr);
if (expected_ptr == actual_ptr) { returntrue;
}
// We asked for an address but didn't get what we wanted, all paths below here should fail. int result = TargetMUnmap(actual_ptr, byte_count); if (result == -1) {
PLOG(WARNING) << StringPrintf("munmap(%p, %zd) failed", actual_ptr, byte_count);
}
if (error_msg != nullptr) { // We call this here so that we can try and generate a full error // message with the overlapping mapping. There's no guarantee that // that there will be an overlap though, since // - The kernel is not *required* to honor expected_ptr unless MAP_FIXED is // true, even if there is no overlap // - There might have been an overlap at the point of mmap, but the // overlapping region has since been unmapped.
// Tell the client the mappings that were in place at the time. if (kIsDebugBuild) {
PrintFileToLog("/proc/self/maps", LogSeverity::WARNING);
}
std::ostringstream os;
os << StringPrintf("Failed to mmap at expected address, mapped at " "0x%08" PRIxPTR " instead of 0x%08" PRIxPTR,
actual, expected);
*error_msg = os.str();
} returnfalse;
}
bool MemMap::CheckReservation(uint8_t* expected_ptr,
size_t byte_count, constchar* name, const MemMap& reservation, /*out*/std::string* error_msg) { if (!reservation.IsValid()) {
*error_msg = StringPrintf("Invalid reservation for %s", name); returnfalse;
}
DCHECK_ALIGNED_PARAM(reservation.Begin(), GetPageSize()); if (reservation.Begin() != expected_ptr) {
*error_msg = StringPrintf("Bad image reservation start for %s: %p instead of %p",
name,
reservation.Begin(),
expected_ptr); returnfalse;
} if (byte_count > reservation.Size()) {
*error_msg = StringPrintf("Insufficient reservation, required %zu, available %zu",
byte_count,
reservation.Size()); returnfalse;
} returntrue;
}
#if USE_ART_LOW_4G_ALLOCATOR void* MemMap::TryMemMapLow4GB(void* ptr,
size_t page_aligned_byte_count, int prot, int flags, int fd,
off_t offset) { void* actual = TargetMMap(ptr, page_aligned_byte_count, prot, flags, fd, offset); if (actual != MAP_FAILED) { // Since we didn't use MAP_FIXED the kernel may have mapped it somewhere not in the low // 4GB. If this is the case, unmap and retry. if (reinterpret_cast<uintptr_t>(actual) + page_aligned_byte_count >= 4 * GB) {
TargetMUnmap(actual, page_aligned_byte_count);
actual = MAP_FAILED;
}
} return actual;
} #endif
std::string MemMap::FormatDebugName(constchar* name) { // If the combined debug name exceeds the kernel limit, we ellipsize the // name interior; we want to keep the end of the name to ensure preservation // of any file extensions that might be used in bookkeeping. // TODO(b/494278476): Also sanitize any invalid characters (e.g., `\`, `$`). static constexpr std::string_view kPrefix = "dalvik-"; static constexpr std::string_view kEllipsis = "..."; static constexpr size_t kMaxKernelLen = 79; // 80 bytes total - 1 for '\0' static constexpr size_t kHeadLen = 20; static constexpr size_t kTailLen =
kMaxKernelLen - kPrefix.length() - kEllipsis.length() - kHeadLen;
void MemMap::SetDebugName(void* map_ptr, constchar* name, size_t size) { // Debug naming is only used for Android target builds. For Linux targets, // we'll still call prctl but it wont do anything till we upstream the prctl. if (kIsTargetFuchsia || !kIsTargetBuild) { return;
}
int flags = MAP_PRIVATE | MAP_ANONYMOUS; if (reuse) { // reuse means it is okay that it overlaps an existing page mapping. // Only use this if you actually made the page reservation yourself.
CHECK(addr != nullptr);
DCHECK(reservation == nullptr);
// We need to store and potentially set an error number for pretty printing of errors int saved_errno = 0;
void* actual = nullptr;
#ifdefined(__linux__) // Recent kernels have a bug where the address hint might be ignored. // See https://lore.kernel.org/all/20241115215256.578125-1-kaleshsingh@google.com/ // We use MAP_FIXED_NOREPLACE to tell the kernel it must allocate at the address or fail. // If the fixed-address allocation fails, we fallback to the default path (random address). // Therefore, non-null 'addr' still behaves as hint-only as far as ART api is concerned. if ((flags & MAP_FIXED) == 0 && addr != nullptr && IsKernelVersionAtLeast(4, 17)) {
actual = MapInternal(
addr, page_aligned_byte_count, prot, flags | MAP_FIXED_NOREPLACE, fd.get(), 0, low_4gb);
} #endif// __linux__
if (actual == nullptr || actual == MAP_FAILED) {
actual = MapInternal(addr, page_aligned_byte_count, prot, flags, fd.get(), 0, low_4gb);
}
saved_errno = errno;
if (actual == MAP_FAILED) { if (error_msg != nullptr) {
PrintFileToLog("/proc/self/maps", LogSeverity::WARNING);
*error_msg = StringPrintf("Failed anonymous mmap(%p, %zd, 0x%x, 0x%x, %d, 0): %s. " "See process maps in the log.",
addr,
page_aligned_byte_count,
prot,
flags,
fd.get(),
strerror(saved_errno));
} return Invalid();
} if (!CheckMapRequest(addr, actual, page_aligned_byte_count, error_msg)) { return Invalid();
}
if (use_debug_name) {
SetDebugName(actual, name, page_aligned_byte_count);
}
if (reservation != nullptr) { // Re-mapping was successful, transfer the ownership of the memory to the new MemMap.
DCHECK_EQ(actual, reservation->Begin());
reservation->ReleaseReservedMemory(byte_count);
} return MemMap(name, reinterpret_cast<uint8_t*>(actual),
byte_count,
actual,
page_aligned_byte_count,
prot,
reuse);
}
// Allocate extra 'alignment - GetPageSize()' bytes so that the mapping can be aligned.
MemMap ret = MapAnonymous(name, /*addr=*/nullptr, // AlignBy requires the size to be page-aligned, so // rounding it here. It is corrected afterwards with // SetSize after AlignBy.
RoundUp(byte_count, GetPageSize()) + alignment - GetPageSize(),
prot,
low_4gb, /*reuse=*/false, /*reservation=*/nullptr,
error_msg); if (LIKELY(ret.IsValid())) {
ret.AlignBy(alignment, /*align_both_ends=*/false);
ret.SetSize(byte_count);
DCHECK_EQ(ret.Size(), byte_count);
DCHECK_ALIGNED_PARAM(ret.Begin(), alignment);
} return ret;
}
template<typename A, typename B> static ptrdiff_t PointerDiff(A* a, B* b) { returnstatic_cast<ptrdiff_t>(reinterpret_cast<intptr_t>(a) - reinterpret_cast<intptr_t>(b));
}
bool MemMap::ReplaceWith(MemMap* source, /*out*/std::string* error) { #if !HAVE_MREMAP_SYSCALL
UNUSED(source);
*error = "Cannot perform atomic replace because we are missing the required mremap syscall"; returnfalse; #else// !HAVE_MREMAP_SYSCALL
CHECK(source != nullptr);
CHECK(source->IsValid()); if (!MemMap::kCanReplaceMapping) {
*error = "Unable to perform atomic replace due to runtime environment!"; returnfalse;
} // neither can be reuse. if (source->reuse_ || reuse_) {
*error = "One or both mappings is not a real mmap!"; returnfalse;
} // TODO Support redzones. if (source->redzone_size_ != 0 || redzone_size_ != 0) {
*error = "source and dest have different redzone sizes"; returnfalse;
} // Make sure they have the same offset from the actual mmap'd address if (PointerDiff(BaseBegin(), Begin()) != PointerDiff(source->BaseBegin(), source->Begin())) {
*error = "source starts at a different offset from the mmap. Cannot atomically replace mappings"; returnfalse;
} // mremap doesn't allow the final [start, end] to overlap with the initial [start, end] (it's like // memcpy but the check is explicit and actually done). if (source->BaseBegin() > BaseBegin() && reinterpret_cast<uint8_t*>(BaseBegin()) + source->BaseSize() > reinterpret_cast<uint8_t*>(source->BaseBegin())) {
*error = "destination memory pages overlap with source memory pages"; returnfalse;
} // Change the protection to match the new location. int old_prot = source->GetProtect(); if (!source->Protect(GetProtect())) {
*error = "Could not change protections for source to those required for dest."; returnfalse;
}
// Do the mremap. void* res = mremap(/*old_address*/source->BaseBegin(), /*old_size*/source->BaseSize(), /*new_size*/source->BaseSize(), /*flags*/MREMAP_MAYMOVE | MREMAP_FIXED, /*new_address*/BaseBegin()); if (res == MAP_FAILED) { int saved_errno = errno; // Wasn't able to move mapping. Change the protection of source back to the original one and // return.
source->Protect(old_prot);
*error = std::string("Failed to mremap source to dest. Error was ") + strerror(saved_errno); returnfalse;
}
CHECK(res == BaseBegin());
// The new base_size is all the pages of the 'source' plus any remaining dest pages. We will unmap // them later.
size_t new_base_size = std::max(source->base_size_, base_size_);
// Invalidate *source, don't unmap it though since it is already gone.
size_t source_size = source->size_;
source->Invalidate();
size_ = source_size;
base_size_ = new_base_size; // Reduce base_size if needed (this will unmap the extra pages).
SetSize(source_size);
// Note that we do not allow MAP_FIXED unless reuse == true or we have an existing // reservation, i.e we expect this mapping to be contained within an existing map. if (reuse && expected_ptr != nullptr) { // reuse means it is okay that it overlaps an existing page mapping. // Only use this if you actually made the page reservation yourself.
DCHECK(reservation == nullptr);
DCHECK(error_msg != nullptr);
DCHECK(ContainedWithinExistingMap(expected_ptr, byte_count, error_msg))
<< ((error_msg != nullptr) ? *error_msg : std::string());
flags |= MAP_FIXED;
} elseif (reservation != nullptr) {
DCHECK(error_msg != nullptr); if (!CheckReservation(expected_ptr, byte_count, filename, *reservation, error_msg)) { return Invalid();
}
flags |= MAP_FIXED;
} else {
CHECK_EQ(0, flags & MAP_FIXED); // Don't bother checking for an overlapping region here. We'll // check this if required after the fact inside CheckMapRequest.
}
if (byte_count == 0) {
*error_msg = "Empty MemMap requested"; return Invalid();
} // Adjust 'offset' to be page-aligned as required by mmap. int page_offset = start % GetPageSize();
off_t page_aligned_offset = start - page_offset; // Adjust 'byte_count' to be page-aligned as we will map this anyway.
size_t page_aligned_byte_count = RoundUp(byte_count + page_offset, GetPageSize()); // The 'expected_ptr' is modified (if specified, ie non-null) to be page aligned to the file but // not necessarily to virtual memory. mmap will page align 'expected' for us.
uint8_t* page_aligned_expected =
(expected_ptr == nullptr) ? nullptr : (expected_ptr - page_offset);
if (reservation != nullptr) { // Re-mapping was successful, transfer the ownership of the memory to the new MemMap.
DCHECK_EQ(actual, reservation->Begin());
reservation->ReleaseReservedMemory(byte_count);
} return MemMap(filename,
actual + page_offset,
byte_count,
actual,
page_aligned_byte_count,
prot,
reuse,
redzone_size);
}
void MemMap::DoReset() {
DCHECK(IsValid());
size_t real_base_size = base_size_; // Unlike Valgrind, AddressSanitizer requires that all manually poisoned memory is unpoisoned // before it is returned to the system. if (redzone_size_ != 0) { // Add redzone_size_ back to base_size or it will cause a mmap leakage.
real_base_size += redzone_size_;
MEMORY_TOOL_MAKE_UNDEFINED( reinterpret_cast<char*>(base_begin_) + real_base_size - redzone_size_,
redzone_size_);
}
if (!reuse_) {
MEMORY_TOOL_MAKE_UNDEFINED(base_begin_, base_size_); if (!already_unmapped_) { int result = TargetMUnmap(base_begin_, real_base_size); if (result == -1) {
PLOG(FATAL) << "munmap failed";
}
}
}
Invalidate();
}
void MemMap::ResetInForkedProcess() { // This should be called on a map that has MADV_DONTFORK. // The kernel has already unmapped this.
already_unmapped_ = true;
Reset();
}
void MemMap::Invalidate() {
DCHECK(IsValid());
// Remove it from gMaps. // TODO(b/307704260) Move MemMap::Init MemMap::Shutdown out of Runtime init/shutdown. if (mem_maps_lock_ != nullptr) { // Runtime was shutdown.
std::lock_guard<std::mutex> mu(*mem_maps_lock_); auto it = GetGMapsEntry(*this);
gMaps->erase(it);
}
// Mark it as invalid.
base_size_ = 0u;
DCHECK(!IsValid());
}
void MemMap::swap(MemMap& other) { if (IsValid() || other.IsValid()) {
std::lock_guard<std::mutex> mu(*mem_maps_lock_);
DCHECK(gMaps != nullptr); auto this_it = IsValid() ? GetGMapsEntry(*this) : gMaps->end(); auto other_it = other.IsValid() ? GetGMapsEntry(other) : gMaps->end(); if (IsValid()) {
DCHECK(this_it != gMaps->end());
DCHECK_EQ(this_it->second, this);
this_it->second = &other;
} if (other.IsValid()) {
DCHECK(other_it != gMaps->end());
DCHECK_EQ(other_it->second, &other);
other_it->second = this;
} // Swap members with the `mem_maps_lock_` held so that `base_begin_` matches // with the `gMaps` key when other threads try to use `gMaps`.
SwapMembers(other);
} else {
SwapMembers(other);
}
}
MEMORY_TOOL_MAKE_UNDEFINED(tail_base_begin, tail_base_size); // Note: Do not explicitly unmap the tail region, mmap() with MAP_FIXED automatically // removes old mappings for the overlapping region. This makes the operation atomic // and prevents other threads from racing to allocate memory in the requested region.
uint8_t* actual = reinterpret_cast<uint8_t*>(TargetMMap(tail_base_begin,
tail_base_size,
tail_prot,
flags,
fd,
offset)); if (actual == MAP_FAILED) {
*error_msg = StringPrintf("map(%p, %zd, 0x%x, 0x%x, %d, 0) failed: %s. See process " "maps in the log.", tail_base_begin, tail_base_size, tail_prot, flags,
fd, strerror(errno));
PrintFileToLog("/proc/self/maps", LogSeverity::WARNING); return Invalid();
} // Update *this. if (new_base_size == 0u) {
std::lock_guard<std::mutex> mu(*mem_maps_lock_); auto it = GetGMapsEntry(*this);
gMaps->erase(it);
}
if (use_debug_name) {
SetDebugName(actual, tail_name, tail_base_size);
}
size_ = new_size;
base_size_ = new_base_size; // Return the new mapping. return MemMap(tail_name, actual, tail_size, actual, tail_base_size, tail_prot, false);
}
bool MemMap::Sync() { #ifdef _WIN32 // TODO: add FlushViewOfFile support.
PLOG(ERROR) << "MemMap::Sync unsupported on Windows."; returnfalse; #else // Historical note: To avoid Valgrind errors, we temporarily lifted the lower-end noaccess // protection before passing it to msync() when `redzone_size_` was non-null, as Valgrind // only accepts page-aligned base address, and excludes the higher-end noaccess protection // from the msync range. b/27552451. return msync(BaseBegin(), BaseSize(), MS_SYNC) == 0; #endif
}
void MemMap::DumpMapsLocked(std::ostream& os, bool terse) { constauto& mem_maps = *gMaps; if (!terse) {
os << mem_maps; return;
}
// Terse output example: // [MemMap: 0x409be000+0x20P~0x11dP+0x20P~0x61cP+0x20P prot=0x3 LinearAlloc] // [MemMap: 0x451d6000+0x6bP(3) prot=0x3 large object space allocation] // The details: // "+0x20P" means 0x20 pages taken by a single mapping, // "~0x11dP" means a gap of 0x11d pages, // "+0x6bP(3)" means 3 mappings one after another, together taking 0x6b pages.
os << "MemMap:" << std::endl; for (auto it = mem_maps.begin(), maps_end = mem_maps.end(); it != maps_end;) {
MemMap* map = it->second; void* base = it->first;
CHECK_EQ(base, map->BaseBegin());
os << "[MemMap: " << base;
++it; // Merge consecutive maps with the same protect flags and name.
constexpr size_t kMaxGaps = 9;
size_t num_gaps = 0;
size_t num = 1u;
size_t size = map->BaseSize();
CHECK_ALIGNED_PARAM(size, GetPageSize()); void* end = map->BaseEnd(); while (it != maps_end &&
it->second->GetProtect() == map->GetProtect() &&
it->second->GetName() == map->GetName() &&
(it->second->BaseBegin() == end || num_gaps < kMaxGaps)) { if (it->second->BaseBegin() != end) {
++num_gaps;
os << "+0x" << std::hex << (size / GetPageSize()) << "P"; if (num != 1u) {
os << "(" << std::dec << num << ")";
}
size_t gap = reinterpret_cast<uintptr_t>(it->second->BaseBegin()) - reinterpret_cast<uintptr_t>(end);
CHECK_ALIGNED_PARAM(gap, GetPageSize());
os << "~0x" << std::hex << (gap / GetPageSize()) << "P";
num = 0u;
size = 0u;
}
CHECK_ALIGNED_PARAM(it->second->BaseSize(), GetPageSize());
++num;
size += it->second->BaseSize();
end = it->second->BaseEnd();
++it;
}
os << "+0x" << std::hex << (size / GetPageSize()) << "P"; if (num != 1u) {
os << "(" << std::dec << num << ")";
}
os << " prot=0x" << std::hex << map->GetProtect() << " " << map->GetName() << "]" << std::endl;
}
}
bool MemMap::HasMemMap(MemMap& map) { void* base_begin = map.BaseBegin(); for (auto it = gMaps->lower_bound(base_begin), end = gMaps->end();
it != end && it->first == base_begin; ++it) { if (it->second == &map) { returntrue;
}
} returnfalse;
}
MemMap* MemMap::GetLargestMemMapAt(void* address) {
size_t largest_size = 0;
MemMap* largest_map = nullptr;
DCHECK(gMaps != nullptr); for (auto it = gMaps->lower_bound(address), end = gMaps->end();
it != end && it->first == address; ++it) {
MemMap* map = it->second;
CHECK(map != nullptr); if (largest_size < map->BaseSize()) {
largest_size = map->BaseSize();
largest_map = map;
}
} return largest_map;
}
void MemMap::Init() { if (mem_maps_lock_ != nullptr) { // dex2oat calls MemMap::Init twice since its needed before the runtime is created. return;
}
mem_maps_lock_ = new std::mutex(); // Not for thread safety, but for the annotation that gMaps is GUARDED_BY(mem_maps_lock_).
std::lock_guard<std::mutex> mu(*mem_maps_lock_); #ifdef ART_PAGE_SIZE_AGNOSTIC
page_size_ = GetPageSizeSlow(); #endif
CHECK_GE(GetPageSize(), kMinPageSize);
CHECK_LE(GetPageSize(), kMaxPageSize); #if USE_ART_LOW_4G_ALLOCATOR // Initialize linear scan to random position.
CHECK_EQ(next_mem_pos_, 0u);
next_mem_pos_ = GenerateNextMemPos(GetPageSize()); #endif
DCHECK(gMaps == nullptr);
gMaps = new Maps;
void MemMap::Shutdown() { if (mem_maps_lock_ == nullptr) { // If MemMap::Shutdown is called more than once, there is no effect. return;
}
{ // Not for thread safety, but for the annotation that gMaps is GUARDED_BY(mem_maps_lock_).
std::lock_guard<std::mutex> mu(*mem_maps_lock_);
DCHECK(gMaps != nullptr); delete gMaps;
gMaps = nullptr;
} #if USE_ART_LOW_4G_ALLOCATOR
next_mem_pos_ = 0u; #endif delete mem_maps_lock_;
mem_maps_lock_ = nullptr;
}
void* MemMap::MapInternalArtLow4GBAllocator(size_t length, int prot, int flags, int fd,
off_t offset) { #if USE_ART_LOW_4G_ALLOCATOR void* actual = MAP_FAILED;
bool first_run = true;
std::lock_guard<std::mutex> mu(*mem_maps_lock_); for (uintptr_t ptr = next_mem_pos_; ptr < 4 * GB; ptr += GetPageSize()) { // Use gMaps as an optimization to skip over large maps. // Find the first map which is address > ptr. auto it = gMaps->upper_bound(reinterpret_cast<void*>(ptr)); if (it != gMaps->begin()) { auto before_it = it;
--before_it; // Start at the end of the map before the upper bound.
ptr = std::max(ptr, reinterpret_cast<uintptr_t>(before_it->second->BaseEnd()));
CHECK_ALIGNED_PARAM(ptr, GetPageSize());
} while (it != gMaps->end()) { // How much space do we have until the next map?
size_t delta = reinterpret_cast<uintptr_t>(it->first) - ptr; // If the space may be sufficient, break out of the loop. if (delta >= length) { break;
} // Otherwise, skip to the end of the map.
ptr = reinterpret_cast<uintptr_t>(it->second->BaseEnd());
CHECK_ALIGNED_PARAM(ptr, GetPageSize());
++it;
}
// Try to see if we get lucky with this address since none of the ART maps overlap.
actual = TryMemMapLow4GB(reinterpret_cast<void*>(ptr), length, prot, flags, fd, offset); if (actual != MAP_FAILED) {
next_mem_pos_ = reinterpret_cast<uintptr_t>(actual) + length; return actual;
}
if (4U * GB - ptr <= length) { // Not enough memory until 4GB. if (first_run) { // Try another time from the bottom;
ptr = LOW_MEM_START - GetPageSize();
first_run = false; continue;
} else { // Second try failed. break;
}
}
void* MemMap::MapInternal(void* addr,
size_t length, int prot, int flags, int fd,
off_t offset, bool low_4gb) { #ifdef __LP64__ // When requesting low_4g memory and having an expectation, the requested range should fit into // 4GB. if (low_4gb && ( // Start out of bounds.
(reinterpret_cast<uintptr_t>(addr) >> 32) != 0 || // End out of bounds. For simplicity, this will fail for the last page of memory.
((reinterpret_cast<uintptr_t>(addr) + length) >> 32) != 0)) {
LOG(ERROR) << "The requested address space (" << addr << ", "
<< reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) + length)
<< ") cannot fit in low_4gb"; return MAP_FAILED;
} #else
UNUSED(low_4gb); #endif
DCHECK_ALIGNED_PARAM(length, GetPageSize()); // TODO: // A page allocator would be a useful abstraction here, as // 1) It is doubtful that MAP_32BIT on x86_64 is doing the right job for us void* actual = MAP_FAILED; #if USE_ART_LOW_4G_ALLOCATOR // MAP_32BIT only available on x86_64. if (low_4gb && addr == nullptr) { // The linear-scan allocator has an issue when executable pages are denied (e.g., by selinux // policies in sensitive processes). In that case, the error code will still be ENOMEM. So // the allocator will scan all low 4GB twice, and still fail. This is *very* slow. // // To avoid the issue, always map non-executable first, and mprotect if necessary. constint orig_prot = prot; constint prot_non_exec = prot & ~PROT_EXEC;
actual = MapInternalArtLow4GBAllocator(length, prot_non_exec, flags, fd, offset);
if (actual == MAP_FAILED) { return MAP_FAILED;
}
// See if we need to remap with the executable bit now. if (orig_prot != prot_non_exec) { if (mprotect(actual, length, orig_prot) != 0) {
PLOG(ERROR) << "Could not protect to requested prot: " << orig_prot;
TargetMUnmap(actual, length);
errno = ENOMEM; return MAP_FAILED;
}
} return actual;
}
void MemMap::TryReadable() { if (base_begin_ == nullptr && base_size_ == 0) { return;
}
CHECK_NE(prot_ & PROT_READ, 0); volatile uint8_t* begin = reinterpret_cast<volatile uint8_t*>(base_begin_); volatile uint8_t* end = begin + base_size_;
DCHECK(IsAlignedParam(begin, GetPageSize()));
DCHECK(IsAlignedParam(end, GetPageSize())); // Read the first byte of each page. Use volatile to prevent the compiler from optimizing away the // reads. for (volatile uint8_t* ptr = begin; ptr < end; ptr += GetPageSize()) { // This read could fault if protection wasn't set correctly.
uint8_t value = *ptr;
UNUSED(value);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.