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