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