aboutsummaryrefslogtreecommitdiff
path: root/tools/debugserver/source/MacOSX/MachVMMemory.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/debugserver/source/MacOSX/MachVMMemory.cpp')
-rw-r--r--tools/debugserver/source/MacOSX/MachVMMemory.cpp598
1 files changed, 598 insertions, 0 deletions
diff --git a/tools/debugserver/source/MacOSX/MachVMMemory.cpp b/tools/debugserver/source/MacOSX/MachVMMemory.cpp
new file mode 100644
index 000000000000..3b86a83024d9
--- /dev/null
+++ b/tools/debugserver/source/MacOSX/MachVMMemory.cpp
@@ -0,0 +1,598 @@
+//===-- MachVMMemory.cpp ----------------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// Created by Greg Clayton on 6/26/07.
+//
+//===----------------------------------------------------------------------===//
+
+#include "MachVMMemory.h"
+#include "MachVMRegion.h"
+#include "DNBLog.h"
+#include <mach/mach_vm.h>
+#include <mach/shared_region.h>
+#include <sys/sysctl.h>
+#include <dlfcn.h>
+
+static const vm_size_t kInvalidPageSize = ~0;
+
+MachVMMemory::MachVMMemory() :
+ m_page_size (kInvalidPageSize),
+ m_err (0)
+{
+}
+
+MachVMMemory::~MachVMMemory()
+{
+}
+
+nub_size_t
+MachVMMemory::PageSize(task_t task)
+{
+ if (m_page_size == kInvalidPageSize)
+ {
+#if defined (TASK_VM_INFO) && TASK_VM_INFO >= 22
+ if (task != TASK_NULL)
+ {
+ kern_return_t kr;
+ mach_msg_type_number_t info_count = TASK_VM_INFO_COUNT;
+ task_vm_info_data_t vm_info;
+ kr = task_info (task, TASK_VM_INFO, (task_info_t) &vm_info, &info_count);
+ if (kr == KERN_SUCCESS)
+ {
+ DNBLogThreadedIf(LOG_TASK, "MachVMMemory::PageSize task_info returned page size of 0x%x", (int) vm_info.page_size);
+ m_page_size = vm_info.page_size;
+ return m_page_size;
+ }
+ else
+ {
+ DNBLogThreadedIf(LOG_TASK, "MachVMMemory::PageSize task_info call failed to get page size, TASK_VM_INFO %d, TASK_VM_INFO_COUNT %d, kern return %d", TASK_VM_INFO, TASK_VM_INFO_COUNT, kr);
+ }
+ }
+#endif
+ m_err = ::host_page_size( ::mach_host_self(), &m_page_size);
+ if (m_err.Fail())
+ m_page_size = 0;
+ }
+ return m_page_size;
+}
+
+nub_size_t
+MachVMMemory::MaxBytesLeftInPage(task_t task, nub_addr_t addr, nub_size_t count)
+{
+ const nub_size_t page_size = PageSize(task);
+ if (page_size > 0)
+ {
+ nub_size_t page_offset = (addr % page_size);
+ nub_size_t bytes_left_in_page = page_size - page_offset;
+ if (count > bytes_left_in_page)
+ count = bytes_left_in_page;
+ }
+ return count;
+}
+
+nub_bool_t
+MachVMMemory::GetMemoryRegionInfo(task_t task, nub_addr_t address, DNBRegionInfo *region_info)
+{
+ MachVMRegion vmRegion(task);
+
+ if (vmRegion.GetRegionForAddress(address))
+ {
+ region_info->addr = vmRegion.StartAddress();
+ region_info->size = vmRegion.GetByteSize();
+ region_info->permissions = vmRegion.GetDNBPermissions();
+ }
+ else
+ {
+ region_info->addr = address;
+ region_info->size = 0;
+ if (vmRegion.GetError().Success())
+ {
+ // vmRegion.GetRegionForAddress() return false, indicating that "address"
+ // wasn't in a valid region, but the "vmRegion" info was successfully
+ // read from the task which means the info describes the next valid
+ // region from which we can infer the size of this invalid region
+ mach_vm_address_t start_addr = vmRegion.StartAddress();
+ if (address < start_addr)
+ region_info->size = start_addr - address;
+ }
+ // If we can't get any info about the size from the next region it means
+ // we asked about an address that was past all mappings, so the size
+ // of this region will take up all remaining address space.
+ if (region_info->size == 0)
+ region_info->size = INVALID_NUB_ADDRESS - region_info->addr;
+
+ // Not readable, writeable or executable
+ region_info->permissions = 0;
+ }
+ return true;
+}
+
+// For integrated graphics chip, this makes the accounting info for 'wired' memory more like top.
+uint64_t
+MachVMMemory::GetStolenPages(task_t task)
+{
+ static uint64_t stolenPages = 0;
+ static bool calculated = false;
+ if (calculated) return stolenPages;
+
+ static int mib_reserved[CTL_MAXNAME];
+ static int mib_unusable[CTL_MAXNAME];
+ static int mib_other[CTL_MAXNAME];
+ static size_t mib_reserved_len = 0;
+ static size_t mib_unusable_len = 0;
+ static size_t mib_other_len = 0;
+ int r;
+
+ /* This can be used for testing: */
+ //tsamp->pages_stolen = (256 * 1024 * 1024ULL) / tsamp->pagesize;
+
+ if(0 == mib_reserved_len)
+ {
+ mib_reserved_len = CTL_MAXNAME;
+
+ r = sysctlnametomib("machdep.memmap.Reserved", mib_reserved,
+ &mib_reserved_len);
+
+ if(-1 == r)
+ {
+ mib_reserved_len = 0;
+ return 0;
+ }
+
+ mib_unusable_len = CTL_MAXNAME;
+
+ r = sysctlnametomib("machdep.memmap.Unusable", mib_unusable,
+ &mib_unusable_len);
+
+ if(-1 == r)
+ {
+ mib_reserved_len = 0;
+ return 0;
+ }
+
+
+ mib_other_len = CTL_MAXNAME;
+
+ r = sysctlnametomib("machdep.memmap.Other", mib_other,
+ &mib_other_len);
+
+ if(-1 == r)
+ {
+ mib_reserved_len = 0;
+ return 0;
+ }
+ }
+
+ if(mib_reserved_len > 0 && mib_unusable_len > 0 && mib_other_len > 0)
+ {
+ uint64_t reserved = 0, unusable = 0, other = 0;
+ size_t reserved_len;
+ size_t unusable_len;
+ size_t other_len;
+
+ reserved_len = sizeof(reserved);
+ unusable_len = sizeof(unusable);
+ other_len = sizeof(other);
+
+ /* These are all declared as QUAD/uint64_t sysctls in the kernel. */
+
+ if (sysctl (mib_reserved,
+ static_cast<u_int>(mib_reserved_len),
+ &reserved,
+ &reserved_len,
+ NULL,
+ 0))
+ {
+ return 0;
+ }
+
+ if (sysctl (mib_unusable,
+ static_cast<u_int>(mib_unusable_len),
+ &unusable,
+ &unusable_len,
+ NULL,
+ 0))
+ {
+ return 0;
+ }
+
+ if (sysctl (mib_other,
+ static_cast<u_int>(mib_other_len),
+ &other,
+ &other_len,
+ NULL,
+ 0))
+ {
+ return 0;
+ }
+
+ if (reserved_len == sizeof(reserved) &&
+ unusable_len == sizeof(unusable) &&
+ other_len == sizeof(other))
+ {
+ uint64_t stolen = reserved + unusable + other;
+ uint64_t mb128 = 128 * 1024 * 1024ULL;
+
+ if(stolen >= mb128)
+ {
+ stolen = (stolen & ~((128 * 1024 * 1024ULL) - 1)); // rounding down
+ stolenPages = stolen / PageSize (task);
+ }
+ }
+ }
+
+ calculated = true;
+ return stolenPages;
+}
+
+static uint64_t GetPhysicalMemory()
+{
+ // This doesn't change often at all. No need to poll each time.
+ static uint64_t physical_memory = 0;
+ static bool calculated = false;
+ if (calculated) return physical_memory;
+
+ size_t len = sizeof(physical_memory);
+ sysctlbyname("hw.memsize", &physical_memory, &len, NULL, 0);
+
+ calculated = true;
+ return physical_memory;
+}
+
+// rsize and dirty_size is not adjusted for dyld shared cache and multiple __LINKEDIT segment, as in vmmap. In practice, dirty_size doesn't differ much but rsize may. There is performance penalty for the adjustment. Right now, only use the dirty_size.
+void
+MachVMMemory::GetRegionSizes(task_t task, mach_vm_size_t &rsize, mach_vm_size_t &dirty_size)
+{
+#if defined (TASK_VM_INFO) && TASK_VM_INFO >= 22
+
+ task_vm_info_data_t vm_info;
+ mach_msg_type_number_t info_count;
+ kern_return_t kr;
+
+ info_count = TASK_VM_INFO_COUNT;
+ kr = task_info(task, TASK_VM_INFO_PURGEABLE, (task_info_t)&vm_info, &info_count);
+ if (kr == KERN_SUCCESS)
+ dirty_size = vm_info.internal;
+#endif
+}
+
+// Test whether the virtual address is within the architecture's shared region.
+static bool InSharedRegion(mach_vm_address_t addr, cpu_type_t type)
+{
+ mach_vm_address_t base = 0, size = 0;
+
+ switch(type) {
+#if defined (CPU_TYPE_ARM64) && defined (SHARED_REGION_BASE_ARM64)
+ case CPU_TYPE_ARM64:
+ base = SHARED_REGION_BASE_ARM64;
+ size = SHARED_REGION_SIZE_ARM64;
+ break;
+#endif
+
+ case CPU_TYPE_ARM:
+ base = SHARED_REGION_BASE_ARM;
+ size = SHARED_REGION_SIZE_ARM;
+ break;
+
+ case CPU_TYPE_X86_64:
+ base = SHARED_REGION_BASE_X86_64;
+ size = SHARED_REGION_SIZE_X86_64;
+ break;
+
+ case CPU_TYPE_I386:
+ base = SHARED_REGION_BASE_I386;
+ size = SHARED_REGION_SIZE_I386;
+ break;
+
+ default: {
+ // Log error abut unknown CPU type
+ break;
+ }
+ }
+
+
+ return(addr >= base && addr < (base + size));
+}
+
+void
+MachVMMemory::GetMemorySizes(task_t task, cpu_type_t cputype, nub_process_t pid, mach_vm_size_t &rprvt, mach_vm_size_t &vprvt)
+{
+ // Collecting some other info cheaply but not reporting for now.
+ mach_vm_size_t empty = 0;
+ mach_vm_size_t fw_private = 0;
+
+ mach_vm_size_t aliased = 0;
+ bool global_shared_text_data_mapped = false;
+ vm_size_t pagesize = PageSize (task);
+
+ for (mach_vm_address_t addr=0, size=0; ; addr += size)
+ {
+ vm_region_top_info_data_t info;
+ mach_msg_type_number_t count = VM_REGION_TOP_INFO_COUNT;
+ mach_port_t object_name;
+
+ kern_return_t kr = mach_vm_region(task, &addr, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&info, &count, &object_name);
+ if (kr != KERN_SUCCESS) break;
+
+ if (InSharedRegion(addr, cputype))
+ {
+ // Private Shared
+ fw_private += info.private_pages_resident * pagesize;
+
+ // Check if this process has the globally shared text and data regions mapped in. If so, set global_shared_text_data_mapped to TRUE and avoid checking again.
+ if (global_shared_text_data_mapped == FALSE && info.share_mode == SM_EMPTY) {
+ vm_region_basic_info_data_64_t b_info;
+ mach_vm_address_t b_addr = addr;
+ mach_vm_size_t b_size = size;
+ count = VM_REGION_BASIC_INFO_COUNT_64;
+
+ kr = mach_vm_region(task, &b_addr, &b_size, VM_REGION_BASIC_INFO, (vm_region_info_t)&b_info, &count, &object_name);
+ if (kr != KERN_SUCCESS) break;
+
+ if (b_info.reserved) {
+ global_shared_text_data_mapped = TRUE;
+ }
+ }
+
+ // Short circuit the loop if this isn't a shared private region, since that's the only region type we care about within the current address range.
+ if (info.share_mode != SM_PRIVATE)
+ {
+ continue;
+ }
+ }
+
+ // Update counters according to the region type.
+ if (info.share_mode == SM_COW && info.ref_count == 1)
+ {
+ // Treat single reference SM_COW as SM_PRIVATE
+ info.share_mode = SM_PRIVATE;
+ }
+
+ switch (info.share_mode)
+ {
+ case SM_LARGE_PAGE:
+ // Treat SM_LARGE_PAGE the same as SM_PRIVATE
+ // since they are not shareable and are wired.
+ case SM_PRIVATE:
+ rprvt += info.private_pages_resident * pagesize;
+ rprvt += info.shared_pages_resident * pagesize;
+ vprvt += size;
+ break;
+
+ case SM_EMPTY:
+ empty += size;
+ break;
+
+ case SM_COW:
+ case SM_SHARED:
+ {
+ if (pid == 0)
+ {
+ // Treat kernel_task specially
+ if (info.share_mode == SM_COW)
+ {
+ rprvt += info.private_pages_resident * pagesize;
+ vprvt += size;
+ }
+ break;
+ }
+
+ if (info.share_mode == SM_COW)
+ {
+ rprvt += info.private_pages_resident * pagesize;
+ vprvt += info.private_pages_resident * pagesize;
+ }
+ break;
+ }
+ default:
+ // log that something is really bad.
+ break;
+ }
+ }
+
+ rprvt += aliased;
+}
+
+static void
+GetPurgeableAndAnonymous(task_t task, uint64_t &purgeable, uint64_t &anonymous)
+{
+#if defined (TASK_VM_INFO) && TASK_VM_INFO >= 22
+
+ kern_return_t kr;
+ mach_msg_type_number_t info_count;
+ task_vm_info_data_t vm_info;
+
+ info_count = TASK_VM_INFO_COUNT;
+ kr = task_info(task, TASK_VM_INFO_PURGEABLE, (task_info_t)&vm_info, &info_count);
+ if (kr == KERN_SUCCESS)
+ {
+ purgeable = vm_info.purgeable_volatile_resident;
+ anonymous = vm_info.internal + vm_info.compressed - vm_info.purgeable_volatile_pmap;
+ }
+
+#endif
+}
+
+#if defined (HOST_VM_INFO64_COUNT)
+nub_bool_t
+MachVMMemory::GetMemoryProfile(DNBProfileDataScanType scanType, task_t task, struct task_basic_info ti, cpu_type_t cputype, nub_process_t pid, vm_statistics64_data_t &vminfo, uint64_t &physical_memory, mach_vm_size_t &rprvt, mach_vm_size_t &rsize, mach_vm_size_t &vprvt, mach_vm_size_t &vsize, mach_vm_size_t &dirty_size, mach_vm_size_t &purgeable, mach_vm_size_t &anonymous)
+#else
+nub_bool_t
+MachVMMemory::GetMemoryProfile(DNBProfileDataScanType scanType, task_t task, struct task_basic_info ti, cpu_type_t cputype, nub_process_t pid, vm_statistics_data_t &vminfo, uint64_t &physical_memory, mach_vm_size_t &rprvt, mach_vm_size_t &rsize, mach_vm_size_t &vprvt, mach_vm_size_t &vsize, mach_vm_size_t &dirty_size, mach_vm_size_t &purgeable, mach_vm_size_t &anonymous)
+#endif
+{
+ if (scanType & eProfileHostMemory)
+ physical_memory = GetPhysicalMemory();
+
+ if (scanType & eProfileMemory)
+ {
+ static mach_port_t localHost = mach_host_self();
+#if defined (HOST_VM_INFO64_COUNT)
+ mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
+ host_statistics64(localHost, HOST_VM_INFO64, (host_info64_t)&vminfo, &count);
+#else
+ mach_msg_type_number_t count = HOST_VM_INFO_COUNT;
+ host_statistics(localHost, HOST_VM_INFO, (host_info_t)&vminfo, &count);
+ vminfo.wire_count += GetStolenPages(task);
+#endif
+
+ /* We are no longer reporting these. Let's not waste time.
+ GetMemorySizes(task, cputype, pid, rprvt, vprvt);
+ rsize = ti.resident_size;
+ vsize = ti.virtual_size;
+
+ if (scanType & eProfileMemoryDirtyPage)
+ {
+ // This uses vmmap strategy. We don't use the returned rsize for now. We prefer to match top's version since that's what we do for the rest of the metrics.
+ GetRegionSizes(task, rsize, dirty_size);
+ }
+ */
+
+ if (scanType & eProfileMemoryAnonymous)
+ {
+ GetPurgeableAndAnonymous(task, purgeable, anonymous);
+ }
+ }
+
+ return true;
+}
+
+nub_size_t
+MachVMMemory::Read(task_t task, nub_addr_t address, void *data, nub_size_t data_count)
+{
+ if (data == NULL || data_count == 0)
+ return 0;
+
+ nub_size_t total_bytes_read = 0;
+ nub_addr_t curr_addr = address;
+ uint8_t *curr_data = (uint8_t*)data;
+ while (total_bytes_read < data_count)
+ {
+ mach_vm_size_t curr_size = MaxBytesLeftInPage(task, curr_addr, data_count - total_bytes_read);
+ mach_msg_type_number_t curr_bytes_read = 0;
+ vm_offset_t vm_memory = 0;
+ m_err = ::mach_vm_read (task, curr_addr, curr_size, &vm_memory, &curr_bytes_read);
+
+ if (DNBLogCheckLogBit(LOG_MEMORY))
+ m_err.LogThreaded("::mach_vm_read ( task = 0x%4.4x, addr = 0x%8.8llx, size = %llu, data => %8.8p, dataCnt => %i )", task, (uint64_t)curr_addr, (uint64_t)curr_size, vm_memory, curr_bytes_read);
+
+ if (m_err.Success())
+ {
+ if (curr_bytes_read != curr_size)
+ {
+ if (DNBLogCheckLogBit(LOG_MEMORY))
+ m_err.LogThreaded("::mach_vm_read ( task = 0x%4.4x, addr = 0x%8.8llx, size = %llu, data => %8.8p, dataCnt=>%i ) only read %u of %llu bytes", task, (uint64_t)curr_addr, (uint64_t)curr_size, vm_memory, curr_bytes_read, curr_bytes_read, (uint64_t)curr_size);
+ }
+ ::memcpy (curr_data, (void *)vm_memory, curr_bytes_read);
+ ::vm_deallocate (mach_task_self (), vm_memory, curr_bytes_read);
+ total_bytes_read += curr_bytes_read;
+ curr_addr += curr_bytes_read;
+ curr_data += curr_bytes_read;
+ }
+ else
+ {
+ break;
+ }
+ }
+ return total_bytes_read;
+}
+
+
+nub_size_t
+MachVMMemory::Write(task_t task, nub_addr_t address, const void *data, nub_size_t data_count)
+{
+ MachVMRegion vmRegion(task);
+
+ nub_size_t total_bytes_written = 0;
+ nub_addr_t curr_addr = address;
+ const uint8_t *curr_data = (const uint8_t*)data;
+
+
+ while (total_bytes_written < data_count)
+ {
+ if (vmRegion.GetRegionForAddress(curr_addr))
+ {
+ mach_vm_size_t curr_data_count = data_count - total_bytes_written;
+ mach_vm_size_t region_bytes_left = vmRegion.BytesRemaining(curr_addr);
+ if (region_bytes_left == 0)
+ {
+ break;
+ }
+ if (curr_data_count > region_bytes_left)
+ curr_data_count = region_bytes_left;
+
+ if (vmRegion.SetProtections(curr_addr, curr_data_count, VM_PROT_READ | VM_PROT_WRITE))
+ {
+ nub_size_t bytes_written = WriteRegion(task, curr_addr, curr_data, curr_data_count);
+ if (bytes_written <= 0)
+ {
+ // Error should have already be posted by WriteRegion...
+ break;
+ }
+ else
+ {
+ total_bytes_written += bytes_written;
+ curr_addr += bytes_written;
+ curr_data += bytes_written;
+ }
+ }
+ else
+ {
+ DNBLogThreadedIf(LOG_MEMORY_PROTECTIONS, "Failed to set read/write protections on region for address: [0x%8.8llx-0x%8.8llx)", (uint64_t)curr_addr, (uint64_t)(curr_addr + curr_data_count));
+ break;
+ }
+ }
+ else
+ {
+ DNBLogThreadedIf(LOG_MEMORY_PROTECTIONS, "Failed to get region for address: 0x%8.8llx", (uint64_t)address);
+ break;
+ }
+ }
+
+ return total_bytes_written;
+}
+
+
+nub_size_t
+MachVMMemory::WriteRegion(task_t task, const nub_addr_t address, const void *data, const nub_size_t data_count)
+{
+ if (data == NULL || data_count == 0)
+ return 0;
+
+ nub_size_t total_bytes_written = 0;
+ nub_addr_t curr_addr = address;
+ const uint8_t *curr_data = (const uint8_t*)data;
+ while (total_bytes_written < data_count)
+ {
+ mach_msg_type_number_t curr_data_count = static_cast<mach_msg_type_number_t>(MaxBytesLeftInPage(task, curr_addr, data_count - total_bytes_written));
+ m_err = ::mach_vm_write (task, curr_addr, (pointer_t) curr_data, curr_data_count);
+ if (DNBLogCheckLogBit(LOG_MEMORY) || m_err.Fail())
+ m_err.LogThreaded("::mach_vm_write ( task = 0x%4.4x, addr = 0x%8.8llx, data = %8.8p, dataCnt = %u )", task, (uint64_t)curr_addr, curr_data, curr_data_count);
+
+#if !defined (__i386__) && !defined (__x86_64__)
+ vm_machine_attribute_val_t mattr_value = MATTR_VAL_CACHE_FLUSH;
+
+ m_err = ::vm_machine_attribute (task, curr_addr, curr_data_count, MATTR_CACHE, &mattr_value);
+ if (DNBLogCheckLogBit(LOG_MEMORY) || m_err.Fail())
+ m_err.LogThreaded("::vm_machine_attribute ( task = 0x%4.4x, addr = 0x%8.8llx, size = %u, attr = MATTR_CACHE, mattr_value => MATTR_VAL_CACHE_FLUSH )", task, (uint64_t)curr_addr, curr_data_count);
+#endif
+
+ if (m_err.Success())
+ {
+ total_bytes_written += curr_data_count;
+ curr_addr += curr_data_count;
+ curr_data += curr_data_count;
+ }
+ else
+ {
+ break;
+ }
+ }
+ return total_bytes_written;
+}