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