MachVMMemory.cpp revision cec43ab7f30085ac7f65a26a58b956a69e363a3b
1//===-- MachVMMemory.cpp ----------------------------------------*- C++ -*-===// 2// 3// The LLVM Compiler Infrastructure 4// 5// This file is distributed under the University of Illinois Open Source 6// License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9// 10// Created by Greg Clayton on 6/26/07. 11// 12//===----------------------------------------------------------------------===// 13 14#include "MachVMMemory.h" 15#include "MachVMRegion.h" 16#include "DNBLog.h" 17#include <mach/mach_vm.h> 18#include <mach/shared_region.h> 19#include <sys/sysctl.h> 20 21MachVMMemory::MachVMMemory() : 22 m_page_size (kInvalidPageSize), 23 m_err (0) 24{ 25} 26 27MachVMMemory::~MachVMMemory() 28{ 29} 30 31nub_size_t 32MachVMMemory::PageSize() 33{ 34 if (m_page_size == kInvalidPageSize) 35 { 36 m_err = ::host_page_size( ::mach_host_self(), &m_page_size); 37 if (m_err.Fail()) 38 m_page_size = 0; 39 } 40 return m_page_size; 41} 42 43nub_size_t 44MachVMMemory::MaxBytesLeftInPage(nub_addr_t addr, nub_size_t count) 45{ 46 const nub_size_t page_size = PageSize(); 47 if (page_size > 0) 48 { 49 nub_size_t page_offset = (addr % page_size); 50 nub_size_t bytes_left_in_page = page_size - page_offset; 51 if (count > bytes_left_in_page) 52 count = bytes_left_in_page; 53 } 54 return count; 55} 56 57nub_bool_t 58MachVMMemory::GetMemoryRegionInfo(task_t task, nub_addr_t address, DNBRegionInfo *region_info) 59{ 60 MachVMRegion vmRegion(task); 61 62 if (vmRegion.GetRegionForAddress(address)) 63 { 64 region_info->addr = vmRegion.StartAddress(); 65 region_info->size = vmRegion.GetByteSize(); 66 region_info->permissions = vmRegion.GetDNBPermissions(); 67 } 68 else 69 { 70 region_info->addr = address; 71 region_info->size = 0; 72 if (vmRegion.GetError().Success()) 73 { 74 // vmRegion.GetRegionForAddress() return false, indicating that "address" 75 // wasn't in a valid region, but the "vmRegion" info was successfully 76 // read from the task which means the info describes the next valid 77 // region from which we can infer the size of this invalid region 78 mach_vm_address_t start_addr = vmRegion.StartAddress(); 79 if (address < start_addr) 80 region_info->size = start_addr - address; 81 } 82 // If we can't get any infor about the size from the next region, just fill 83 // 1 in as the byte size 84 if (region_info->size == 0) 85 region_info->size = 1; 86 87 // Not readable, writeable or executable 88 region_info->permissions = 0; 89 } 90 return true; 91} 92 93// For integrated graphics chip, this makes the accounting info for 'wired' memory more like top. 94static uint64_t GetStolenPages() 95{ 96 static uint64_t stolenPages = 0; 97 static bool calculated = false; 98 if (calculated) return stolenPages; 99 100 static int mib_reserved[CTL_MAXNAME]; 101 static int mib_unusable[CTL_MAXNAME]; 102 static int mib_other[CTL_MAXNAME]; 103 static size_t mib_reserved_len = 0; 104 static size_t mib_unusable_len = 0; 105 static size_t mib_other_len = 0; 106 int r; 107 108 /* This can be used for testing: */ 109 //tsamp->pages_stolen = (256 * 1024 * 1024ULL) / tsamp->pagesize; 110 111 if(0 == mib_reserved_len) 112 { 113 mib_reserved_len = CTL_MAXNAME; 114 115 r = sysctlnametomib("machdep.memmap.Reserved", mib_reserved, 116 &mib_reserved_len); 117 118 if(-1 == r) 119 { 120 mib_reserved_len = 0; 121 return 0; 122 } 123 124 mib_unusable_len = CTL_MAXNAME; 125 126 r = sysctlnametomib("machdep.memmap.Unusable", mib_unusable, 127 &mib_unusable_len); 128 129 if(-1 == r) 130 { 131 mib_reserved_len = 0; 132 return 0; 133 } 134 135 136 mib_other_len = CTL_MAXNAME; 137 138 r = sysctlnametomib("machdep.memmap.Other", mib_other, 139 &mib_other_len); 140 141 if(-1 == r) 142 { 143 mib_reserved_len = 0; 144 return 0; 145 } 146 } 147 148 if(mib_reserved_len > 0 && mib_unusable_len > 0 && mib_other_len > 0) 149 { 150 uint64_t reserved = 0, unusable = 0, other = 0; 151 size_t reserved_len; 152 size_t unusable_len; 153 size_t other_len; 154 155 reserved_len = sizeof(reserved); 156 unusable_len = sizeof(unusable); 157 other_len = sizeof(other); 158 159 /* These are all declared as QUAD/uint64_t sysctls in the kernel. */ 160 161 if(-1 == sysctl(mib_reserved, mib_reserved_len, &reserved, 162 &reserved_len, NULL, 0)) 163 { 164 return 0; 165 } 166 167 if(-1 == sysctl(mib_unusable, mib_unusable_len, &unusable, 168 &unusable_len, NULL, 0)) 169 { 170 return 0; 171 } 172 173 if(-1 == sysctl(mib_other, mib_other_len, &other, 174 &other_len, NULL, 0)) 175 { 176 return 0; 177 } 178 179 if(reserved_len == sizeof(reserved) 180 && unusable_len == sizeof(unusable) 181 && other_len == sizeof(other)) 182 { 183 uint64_t stolen = reserved + unusable + other; 184 uint64_t mb128 = 128 * 1024 * 1024ULL; 185 186 if(stolen >= mb128) 187 { 188 stolen = (stolen & ~((128 * 1024 * 1024ULL) - 1)); // rounding down 189 stolenPages = stolen/vm_page_size; 190 } 191 } 192 } 193 194 calculated = true; 195 return stolenPages; 196} 197 198static uint64_t GetPhysicalMemory() 199{ 200 // This doesn't change often at all. No need to poll each time. 201 static uint64_t physical_memory = 0; 202 static bool calculated = false; 203 if (calculated) return physical_memory; 204 205 int mib[2]; 206 mib[0] = CTL_HW; 207 mib[1] = HW_MEMSIZE; 208 size_t len = sizeof(physical_memory); 209 sysctl(mib, 2, &physical_memory, &len, NULL, 0); 210 return physical_memory; 211} 212 213// 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. 214static void GetRegionSizes(task_t task, mach_vm_size_t &rsize, mach_vm_size_t &dirty_size) 215{ 216 mach_vm_address_t address = 0; 217 mach_vm_size_t size; 218 kern_return_t err = 0; 219 unsigned nestingDepth = 0; 220 mach_vm_size_t pages_resident = 0; 221 mach_vm_size_t pages_dirtied = 0; 222 223 while (1) 224 { 225 mach_msg_type_number_t count; 226 struct vm_region_submap_info_64 info; 227 228 count = VM_REGION_SUBMAP_INFO_COUNT_64; 229 err = mach_vm_region_recurse(task, &address, &size, &nestingDepth, (vm_region_info_t)&info, &count); 230 if (err == KERN_INVALID_ADDRESS) 231 { 232 // It seems like this is a good break too. 233 break; 234 } 235 else if (err) 236 { 237 mach_error("vm_region",err); 238 break; // reached last region 239 } 240 241 bool should_count = true; 242 if (info.is_submap) 243 { // is it a submap? 244 nestingDepth++; 245 should_count = false; 246 } 247 else 248 { 249 // Don't count malloc stack logging data in the TOTAL VM usage lines. 250 if (info.user_tag == VM_MEMORY_ANALYSIS_TOOL) 251 should_count = false; 252 // Don't count system shared library region not used by this process. 253 if (address >= SHARED_REGION_BASE && address < (SHARED_REGION_BASE + SHARED_REGION_SIZE)) 254 should_count = false; 255 256 address = address+size; 257 } 258 259 if (should_count) 260 { 261 pages_resident += info.pages_resident; 262 pages_dirtied += info.pages_dirtied; 263 } 264 } 265 266 rsize = pages_resident * vm_page_size; 267 dirty_size = pages_dirtied * vm_page_size; 268} 269 270// Test whether the virtual address is within the architecture's shared region. 271static bool InSharedRegion(mach_vm_address_t addr, cpu_type_t type) 272{ 273 mach_vm_address_t base = 0, size = 0; 274 275 switch(type) { 276 case CPU_TYPE_ARM: 277 base = SHARED_REGION_BASE_ARM; 278 size = SHARED_REGION_SIZE_ARM; 279 break; 280 281 case CPU_TYPE_X86_64: 282 base = SHARED_REGION_BASE_X86_64; 283 size = SHARED_REGION_SIZE_X86_64; 284 break; 285 286 case CPU_TYPE_I386: 287 base = SHARED_REGION_BASE_I386; 288 size = SHARED_REGION_SIZE_I386; 289 break; 290 291 default: { 292 // Log error abut unknown CPU type 293 break; 294 } 295 } 296 297 298 return(addr >= base && addr < (base + size)); 299} 300 301static void GetMemorySizes(task_t task, cpu_type_t cputype, nub_process_t pid, mach_vm_size_t &rprvt, mach_vm_size_t &vprvt) 302{ 303 // Collecting some other info cheaply but not reporting for now. 304 mach_vm_size_t empty = 0; 305 mach_vm_size_t fw_private = 0; 306 307 mach_vm_size_t aliased = 0; 308 mach_vm_size_t pagesize = vm_page_size; 309 bool global_shared_text_data_mapped = false; 310 311 for (mach_vm_address_t addr=0, size=0; ; addr += size) 312 { 313 vm_region_top_info_data_t info; 314 mach_msg_type_number_t count = VM_REGION_TOP_INFO_COUNT; 315 mach_port_t object_name; 316 317 kern_return_t kr = mach_vm_region(task, &addr, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&info, &count, &object_name); 318 if (kr != KERN_SUCCESS) break; 319 320 if (InSharedRegion(addr, cputype)) 321 { 322 // Private Shared 323 fw_private += info.private_pages_resident * pagesize; 324 325 // 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. 326 if (global_shared_text_data_mapped == FALSE && info.share_mode == SM_EMPTY) { 327 vm_region_basic_info_data_64_t b_info; 328 mach_vm_address_t b_addr = addr; 329 mach_vm_size_t b_size = size; 330 count = VM_REGION_BASIC_INFO_COUNT_64; 331 332 kr = mach_vm_region(task, &b_addr, &b_size, VM_REGION_BASIC_INFO, (vm_region_info_t)&b_info, &count, &object_name); 333 if (kr != KERN_SUCCESS) break; 334 335 if (b_info.reserved) { 336 global_shared_text_data_mapped = TRUE; 337 } 338 } 339 340 // 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. 341 if (info.share_mode != SM_PRIVATE) 342 { 343 continue; 344 } 345 } 346 347 // Update counters according to the region type. 348 if (info.share_mode == SM_COW && info.ref_count == 1) 349 { 350 // Treat single reference SM_COW as SM_PRIVATE 351 info.share_mode = SM_PRIVATE; 352 } 353 354 switch (info.share_mode) 355 { 356 case SM_LARGE_PAGE: 357 // Treat SM_LARGE_PAGE the same as SM_PRIVATE 358 // since they are not shareable and are wired. 359 case SM_PRIVATE: 360 rprvt += info.private_pages_resident * pagesize; 361 rprvt += info.shared_pages_resident * pagesize; 362 vprvt += size; 363 break; 364 365 case SM_EMPTY: 366 empty += size; 367 break; 368 369 case SM_COW: 370 case SM_SHARED: 371 { 372 if (pid == 0) 373 { 374 // Treat kernel_task specially 375 if (info.share_mode == SM_COW) 376 { 377 rprvt += info.private_pages_resident * pagesize; 378 vprvt += size; 379 } 380 break; 381 } 382 383 if (info.share_mode == SM_COW) 384 { 385 rprvt += info.private_pages_resident * pagesize; 386 vprvt += info.private_pages_resident * pagesize; 387 } 388 break; 389 } 390 default: 391 // log that something is really bad. 392 break; 393 } 394 } 395 396 rprvt += aliased; 397} 398 399nub_bool_t 400MachVMMemory::GetMemoryProfile(DNBProfileDataScanType scanType, task_t task, struct task_basic_info ti, cpu_type_t cputype, nub_process_t pid, vm_statistics_data_t &vm_stats, 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) 401{ 402 if (scanType & eProfileHostMemory) 403 physical_memory = GetPhysicalMemory(); 404 405 if (scanType & eProfileMemory) 406 { 407 static mach_port_t localHost = mach_host_self(); 408 mach_msg_type_number_t count = HOST_VM_INFO_COUNT; 409 host_statistics(localHost, HOST_VM_INFO, (host_info_t)&vm_stats, &count); 410 vm_stats.wire_count += GetStolenPages(); 411 412 GetMemorySizes(task, cputype, pid, rprvt, vprvt); 413 414 rsize = ti.resident_size; 415 vsize = ti.virtual_size; 416 } 417 418 if (scanType & eProfileMemoryDirtyPage) 419 { 420 // 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. 421 GetRegionSizes(task, rsize, dirty_size); 422 } 423 424 return true; 425} 426 427nub_size_t 428MachVMMemory::Read(task_t task, nub_addr_t address, void *data, nub_size_t data_count) 429{ 430 if (data == NULL || data_count == 0) 431 return 0; 432 433 nub_size_t total_bytes_read = 0; 434 nub_addr_t curr_addr = address; 435 uint8_t *curr_data = (uint8_t*)data; 436 while (total_bytes_read < data_count) 437 { 438 mach_vm_size_t curr_size = MaxBytesLeftInPage(curr_addr, data_count - total_bytes_read); 439 mach_msg_type_number_t curr_bytes_read = 0; 440 vm_offset_t vm_memory = NULL; 441 m_err = ::mach_vm_read (task, curr_addr, curr_size, &vm_memory, &curr_bytes_read); 442 443 if (DNBLogCheckLogBit(LOG_MEMORY)) 444 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); 445 446 if (m_err.Success()) 447 { 448 if (curr_bytes_read != curr_size) 449 { 450 if (DNBLogCheckLogBit(LOG_MEMORY)) 451 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); 452 } 453 ::memcpy (curr_data, (void *)vm_memory, curr_bytes_read); 454 ::vm_deallocate (mach_task_self (), vm_memory, curr_bytes_read); 455 total_bytes_read += curr_bytes_read; 456 curr_addr += curr_bytes_read; 457 curr_data += curr_bytes_read; 458 } 459 else 460 { 461 break; 462 } 463 } 464 return total_bytes_read; 465} 466 467 468nub_size_t 469MachVMMemory::Write(task_t task, nub_addr_t address, const void *data, nub_size_t data_count) 470{ 471 MachVMRegion vmRegion(task); 472 473 nub_size_t total_bytes_written = 0; 474 nub_addr_t curr_addr = address; 475 const uint8_t *curr_data = (const uint8_t*)data; 476 477 478 while (total_bytes_written < data_count) 479 { 480 if (vmRegion.GetRegionForAddress(curr_addr)) 481 { 482 mach_vm_size_t curr_data_count = data_count - total_bytes_written; 483 mach_vm_size_t region_bytes_left = vmRegion.BytesRemaining(curr_addr); 484 if (region_bytes_left == 0) 485 { 486 break; 487 } 488 if (curr_data_count > region_bytes_left) 489 curr_data_count = region_bytes_left; 490 491 if (vmRegion.SetProtections(curr_addr, curr_data_count, VM_PROT_READ | VM_PROT_WRITE)) 492 { 493 nub_size_t bytes_written = WriteRegion(task, curr_addr, curr_data, curr_data_count); 494 if (bytes_written <= 0) 495 { 496 // Error should have already be posted by WriteRegion... 497 break; 498 } 499 else 500 { 501 total_bytes_written += bytes_written; 502 curr_addr += bytes_written; 503 curr_data += bytes_written; 504 } 505 } 506 else 507 { 508 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)); 509 break; 510 } 511 } 512 else 513 { 514 DNBLogThreadedIf(LOG_MEMORY_PROTECTIONS, "Failed to get region for address: 0x%8.8llx", (uint64_t)address); 515 break; 516 } 517 } 518 519 return total_bytes_written; 520} 521 522 523nub_size_t 524MachVMMemory::WriteRegion(task_t task, const nub_addr_t address, const void *data, const nub_size_t data_count) 525{ 526 if (data == NULL || data_count == 0) 527 return 0; 528 529 nub_size_t total_bytes_written = 0; 530 nub_addr_t curr_addr = address; 531 const uint8_t *curr_data = (const uint8_t*)data; 532 while (total_bytes_written < data_count) 533 { 534 mach_msg_type_number_t curr_data_count = MaxBytesLeftInPage(curr_addr, data_count - total_bytes_written); 535 m_err = ::mach_vm_write (task, curr_addr, (pointer_t) curr_data, curr_data_count); 536 if (DNBLogCheckLogBit(LOG_MEMORY) || m_err.Fail()) 537 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); 538 539#if !defined (__i386__) && !defined (__x86_64__) 540 vm_machine_attribute_val_t mattr_value = MATTR_VAL_CACHE_FLUSH; 541 542 m_err = ::vm_machine_attribute (task, curr_addr, curr_data_count, MATTR_CACHE, &mattr_value); 543 if (DNBLogCheckLogBit(LOG_MEMORY) || m_err.Fail()) 544 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); 545#endif 546 547 if (m_err.Success()) 548 { 549 total_bytes_written += curr_data_count; 550 curr_addr += curr_data_count; 551 curr_data += curr_data_count; 552 } 553 else 554 { 555 break; 556 } 557 } 558 return total_bytes_written; 559} 560