| /* | |
| * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * Copyright (c) 2023, 2024, Red Hat, Inc. and/or its affiliates. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| * | |
| */ | |
| #if defined(__APPLE__) | |
| #include "nmt/memMapPrinter.hpp" | |
| #include "runtime/os.hpp" | |
| #include "utilities/align.hpp" | |
| #include "utilities/globalDefinitions.hpp" | |
| #include "utilities/ostream.hpp" | |
| #include "utilities/powerOfTwo.hpp" | |
| #include <limits.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <libproc.h> | |
| #include <unistd.h> | |
| #include <mach/vm_inherit.h> | |
| #include <mach/vm_prot.h> | |
| #include <mach/mach_vm.h> | |
| // maximum number of mapping records returned | |
| static const int MAX_REGIONS_RETURNED = 1000000; | |
| // ::mmap() on MacOS is a layer on top of Mach system calls, and will allocate in 128MB chunks. | |
| // This code will coalesce a series of identical 128GB chunks (maybe followed by one smaller chunk | |
| // with identical flags) into one. | |
| // Unfortunately, two or more identically allocated contiguous sections will appear as one, if the | |
| // first section is size 128MB. vmmap(1) has the same issue. | |
| static const int MACOS_PARTIAL_ALLOCATION_SIZE = 128 * M; | |
| class MappingInfo { | |
| proc_regioninfo _rinfo; | |
| public: | |
| const char* _address; | |
| size_t _size; | |
| stringStream _share_buffer; | |
| stringStream _type_buffer; | |
| stringStream _protect_buffer; | |
| stringStream _file_name; | |
| const char* _tag_text; | |
| MappingInfo() : _address(nullptr), _size(0), _tag_text(nullptr) {} | |
| void reset() { | |
| _share_buffer.reset(); | |
| _protect_buffer.reset(); | |
| _type_buffer.reset(); | |
| _file_name.reset(); | |
| _tag_text = nullptr; | |
| } | |
| bool canCombine(const proc_regionwithpathinfo& mem_info) { | |
| const proc_regioninfo& n = mem_info.prp_prinfo; | |
| bool cc = _rinfo.pri_size == MACOS_PARTIAL_ALLOCATION_SIZE | |
| && n.pri_address == (_rinfo.pri_address + _size) | |
| && n.pri_protection == _rinfo.pri_protection | |
| && n.pri_max_protection == _rinfo.pri_max_protection | |
| && n.pri_user_tag == _rinfo.pri_user_tag | |
| && n.pri_share_mode == _rinfo.pri_share_mode | |
| && n.pri_offset == 0; | |
| return cc; | |
| } | |
| void combineWithFollowing(const proc_regionwithpathinfo& mem_info) { | |
| _size += mem_info.prp_prinfo.pri_size; | |
| } | |
| void process(const proc_regionwithpathinfo& mem_info) { | |
| reset(); | |
| _rinfo = mem_info.prp_prinfo; | |
| _address = (const char*) _rinfo.pri_address; | |
| _size = _rinfo.pri_size; | |
| if (mem_info.prp_vip.vip_path[0] != '\0') { | |
| _file_name.print_raw(mem_info.prp_vip.vip_path); | |
| } | |
| // proc_regionfilename() seems to give bad results, so we don't try to use it here. | |
| char prot[4]; | |
| char maxprot[4]; | |
| rwbits(_rinfo.pri_protection, prot); | |
| rwbits(_rinfo.pri_max_protection, maxprot); | |
| _protect_buffer.print("%s/%s", prot, maxprot); | |
| get_share_mode(_share_buffer, _rinfo); | |
| _tag_text = tagToStr(_rinfo.pri_user_tag); | |
| } | |
| static void get_share_mode(outputStream& out, const proc_regioninfo& rinfo) { | |
| static const char* share_strings[] = { | |
| "cow", "pvt", "---", "shr", "tsh", "p/a", "s/a", "lpg" | |
| }; | |
| assert(SM_COW == 1 && SM_LARGE_PAGE == (sizeof(share_strings)/sizeof(share_strings[0])), "share_mode contants are out of range"); // the +1 offset is intentional; see below | |
| const bool valid_share_mode = rinfo.pri_share_mode >= SM_COW && rinfo.pri_share_mode <= SM_LARGE_PAGE; | |
| if (valid_share_mode) { | |
| int share_mode = rinfo.pri_share_mode; | |
| out.print_raw(share_strings[share_mode - 1]); | |
| } else { | |
| out.print_cr("invalid pri_share_mode (%d)", rinfo.pri_share_mode); | |
| assert(valid_share_mode, "invalid pri_share_mode (%d)", rinfo.pri_share_mode); | |
| } | |
| } | |
| #define X1(TAG, DESCR) X2(TAG, DESCR) | |
| #define X2(TAG, DESCRIPTION) case VM_MEMORY_ ## TAG: return # DESCRIPTION; | |
| static const char* tagToStr(uint32_t user_tag) { | |
| switch (user_tag) { | |
| case 0: | |
| return 0; | |
| X1(MALLOC, malloc); | |
| X1(MALLOC_SMALL, malloc_small); | |
| X1(MALLOC_LARGE, malloc_large); | |
| X1(MALLOC_HUGE, malloc_huge); | |
| X1(SBRK, sbrk); | |
| X1(REALLOC, realloc); | |
| X1(MALLOC_TINY, malloc_tiny); | |
| X1(MALLOC_LARGE_REUSABLE, malloc_large_reusable); | |
| X1(MALLOC_LARGE_REUSED, malloc_lage_reused); | |
| X1(ANALYSIS_TOOL, analysis_tool); | |
| X1(MALLOC_NANO, malloc_nano); | |
| X1(MALLOC_MEDIUM, malloc_medium); | |
| X1(MALLOC_PROB_GUARD, malloc_prob_guard); | |
| X1(MACH_MSG, malloc_msg); | |
| X1(IOKIT, IOKit); | |
| X1(STACK, stack); | |
| X1(GUARD, guard); | |
| X1(SHARED_PMAP, shared_pmap); | |
| X1(DYLIB, dylib); | |
| X1(UNSHARED_PMAP, unshared_pmap); | |
| X2(APPKIT, AppKit); | |
| X2(FOUNDATION, Foundation); | |
| X2(COREGRAPHICS, CoreGraphics); | |
| X2(CORESERVICES, CoreServices); // is also VM_MEMORY_CARBON | |
| X2(JAVA, Java); | |
| X2(COREDATA, CoreData); | |
| X1(COREDATA_OBJECTIDS, CodeData_objectids); | |
| X1(ATS, ats); | |
| X1(DYLD, dyld); | |
| X1(DYLD_MALLOC, dyld_malloc); | |
| X1(SQLITE, sqlite); | |
| X1(JAVASCRIPT_CORE, javascript_core); | |
| X1(JAVASCRIPT_JIT_EXECUTABLE_ALLOCATOR, javascript_jit_executable_allocator); | |
| X1(JAVASCRIPT_JIT_REGISTER_FILE, javascript_jit_register_file); | |
| X1(OPENCL, OpenCL); | |
| X2(COREIMAGE, CoreImage); | |
| X2(IMAGEIO, ImageIO); | |
| X2(COREPROFILE, CoreProfile); | |
| X1(APPLICATION_SPECIFIC_1, application_specific_1); | |
| X1(APPLICATION_SPECIFIC_16, application_specific_16); | |
| X1(OS_ALLOC_ONCE, os_alloc_once); | |
| X1(GENEALOGY, genealogy); | |
| default: | |
| static char buffer[30]; | |
| snprintf(buffer, sizeof(buffer), "user_tag=0x%x(%d)", user_tag, user_tag); | |
| return buffer; | |
| } | |
| } | |
| static void rwbits(int rw, char bits[4]) { | |
| bits[0] = rw & VM_PROT_READ ? 'r' : '-'; | |
| bits[1] = rw & VM_PROT_WRITE ? 'w' : '-'; | |
| bits[2] = rw & VM_PROT_EXECUTE ? 'x' : '-'; | |
| bits[3] = 0; | |
| } | |
| }; | |
| class ProcSmapsSummary { | |
| unsigned _num_mappings; | |
| size_t _private; | |
| size_t _committed; // combined committed size | |
| size_t _reserved; // reserved but not committed | |
| size_t _shared; // combined shared size | |
| size_t _swapped_out; // combined amount of swapped-out memory | |
| public: | |
| ProcSmapsSummary() : _num_mappings(0), _private(0), | |
| _committed(0), _shared(0), _swapped_out(0) {} | |
| void add_mapping(const proc_regioninfo& region_info) { | |
| _num_mappings++; | |
| bool is_private = region_info.pri_share_mode == SM_PRIVATE | |
| || region_info.pri_share_mode == SM_PRIVATE_ALIASED; | |
| bool is_shared = region_info.pri_share_mode == SM_SHARED | |
| || region_info.pri_share_mode == SM_SHARED_ALIASED | |
| || region_info.pri_share_mode == SM_TRUESHARED | |
| || region_info.pri_share_mode == SM_COW; | |
| bool is_committed = region_info.pri_share_mode == SM_EMPTY | |
| && region_info.pri_max_protection == VM_PROT_ALL | |
| && ((region_info.pri_protection & VM_PROT_DEFAULT) == VM_PROT_DEFAULT); | |
| bool is_reserved = region_info.pri_share_mode == SM_EMPTY | |
| && region_info.pri_max_protection == VM_PROT_ALL | |
| && region_info.pri_protection == VM_PROT_NONE; | |
| _private += is_private ? region_info.pri_size : 0; | |
| _shared += is_shared ? region_info.pri_size : 0; | |
| _swapped_out += region_info.pri_pages_swapped_out; | |
| _committed += is_committed ? region_info.pri_size : 0; | |
| _reserved += is_reserved ? region_info.pri_size : 0; | |
| } | |
| void print_on(const MappingPrintSession& session) const { | |
| outputStream* st = session.out(); | |
| st->print_cr("Number of mappings: %u", _num_mappings); | |
| task_vm_info vm_info; | |
| mach_msg_type_number_t num_out = TASK_VM_INFO_COUNT; | |
| kern_return_t err = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)(&vm_info), &num_out); | |
| if (err == KERN_SUCCESS) { | |
| st->print_cr(" vsize: %llu (%llu%s)", vm_info.virtual_size, PROPERFMTARGS(vm_info.virtual_size)); | |
| st->print_cr(" rss: %llu (%llu%s)", vm_info.resident_size, PROPERFMTARGS(vm_info.resident_size)); | |
| st->print_cr(" peak rss: %llu (%llu%s)", vm_info.resident_size_peak, PROPERFMTARGS(vm_info.resident_size_peak)); | |
| st->print_cr(" page size: %d (%ld%s)", vm_info.page_size, PROPERFMTARGS((size_t)vm_info.page_size)); | |
| } else { | |
| st->print_cr("error getting vm_info %d", err); | |
| } | |
| st->print_cr(" reserved: %zu (" PROPERFMT ")", _reserved, PROPERFMTARGS(_reserved)); | |
| st->print_cr(" committed: %zu (" PROPERFMT ")", _committed, PROPERFMTARGS(_committed)); | |
| st->print_cr(" private: %zu (" PROPERFMT ")", _private, PROPERFMTARGS(_private)); | |
| st->print_cr(" shared: %zu (" PROPERFMT ")", _shared, PROPERFMTARGS(_shared)); | |
| st->print_cr(" swapped out: %zu (" PROPERFMT ")", _swapped_out * vm_info.page_size, PROPERFMTARGS(_swapped_out * vm_info.page_size)); | |
| } | |
| }; | |
| class ProcSmapsPrinter { | |
| const MappingPrintSession& _session; | |
| public: | |
| ProcSmapsPrinter(const MappingPrintSession& session) : | |
| _session(session) | |
| {} | |
| void print_single_mapping(const proc_regioninfo& region_info, const MappingInfo& mapping_info) const { | |
| outputStream* st = _session.out(); | |
| #define INDENT_BY(n) \ | |
| if (st->fill_to(n) == 0) { \ | |
| st->print(" "); \ | |
| } | |
| st->print("%#014.12llx-%#014.12llx", (uint64_t)(mapping_info._address), (uint64_t)(mapping_info._address + mapping_info._size)); | |
| INDENT_BY(38); | |
| st->print("%12ld", mapping_info._size); | |
| INDENT_BY(51); | |
| st->print("%s", mapping_info._protect_buffer.base()); | |
| INDENT_BY(59); | |
| st->print("%s", mapping_info._share_buffer.base()); | |
| st->print("%s", mapping_info._type_buffer.base()); | |
| INDENT_BY(64); | |
| st->print("%#11llx", region_info.pri_offset); | |
| INDENT_BY(77); | |
| if (_session.print_nmt_info_for_region((const void*)mapping_info._address, (const void*)(mapping_info._address + mapping_info._size))) { | |
| st->print(" "); | |
| } else { | |
| const char* tag = mapping_info._tag_text; | |
| if (tag != nullptr) { | |
| st->print("[%s] ", tag); | |
| } | |
| } | |
| st->print_raw(mapping_info._file_name.base()); | |
| st->cr(); | |
| #undef INDENT_BY | |
| } | |
| void print_legend() const { | |
| outputStream* st = _session.out(); | |
| st->print_cr("from, to, vsize: address range and size"); | |
| st->print_cr("prot: protection:"); | |
| st->print_cr(" rwx: read / write / execute"); | |
| st->print_cr("share: share mode:"); | |
| st->print_cr(" cow: copy on write"); | |
| st->print_cr(" pvt: private"); | |
| st->print_cr(" shr: shared"); | |
| st->print_cr(" tsh: true shared"); | |
| st->print_cr(" p/a: private aliased"); | |
| st->print_cr(" s/a: shared aliased"); | |
| st->print_cr(" lpg: large page"); | |
| st->print_cr("offset: offset from start of allocation block"); | |
| st->print_cr("vminfo: VM information (requires NMT)"); | |
| st->print_cr("file: file mapped, if mapping is not anonymous"); | |
| { | |
| StreamIndentor si(st, 16); | |
| _session.print_nmt_flag_legend(); | |
| } | |
| st->print_cr("file: file mapped, if mapping is not anonymous"); | |
| } | |
| void print_header() const { | |
| outputStream* st = _session.out(); | |
| // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 | |
| // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 | |
| // 0x000102890000-0x000102898000 32768 r--/r-- cow 0xc000 /Users/simont/dev/openjdk/jdk/build/macos-aarch64-fastdebug-shenandoah/images/jdk/bin/java | |
| st->print_cr("from to vsize prot share offset vminfo/file"); | |
| st->print_cr("=================================================================================================="); | |
| } | |
| }; | |
| static bool is_interesting(const proc_regionwithpathinfo& info) { | |
| return info.prp_prinfo.pri_share_mode != SM_EMPTY | |
| || info.prp_prinfo.pri_user_tag != 0 | |
| || info.prp_vip.vip_path[0] != '\0' | |
| || info.prp_prinfo.pri_protection != 0 | |
| || info.prp_prinfo.pri_max_protection != 0; | |
| } | |
| void MemMapPrinter::pd_print_all_mappings(const MappingPrintSession& session) { | |
| ProcSmapsPrinter printer(session); | |
| ProcSmapsSummary summary; | |
| outputStream* const st = session.out(); | |
| const pid_t pid = getpid(); | |
| printer.print_legend(); | |
| st->cr(); | |
| printer.print_header(); | |
| proc_regionwithpathinfo region_info_with_path; | |
| MappingInfo mapping_info; | |
| uint64_t address = 0; | |
| int region_count = 0; | |
| while (true) { | |
| if (++region_count > MAX_REGIONS_RETURNED) { | |
| st->print_cr("limit of %d regions reached (results inaccurate)", region_count); | |
| break; | |
| } | |
| ::bzero(®ion_info_with_path, sizeof(region_info_with_path)); | |
| int retval = proc_pidinfo(pid, PROC_PIDREGIONPATHINFO, (uint64_t)address, ®ion_info_with_path, sizeof(region_info_with_path)); | |
| if (retval <= 0) { | |
| break; | |
| } else if (retval < (int)sizeof(region_info_with_path)) { | |
| st->print_cr("proc_pidinfo() returned %d", retval); | |
| assert(false, "proc_pidinfo() returned %d", retval); | |
| } | |
| proc_regioninfo& region_info = region_info_with_path.prp_prinfo; | |
| if (is_interesting(region_info_with_path)) { | |
| if (mapping_info.canCombine(region_info_with_path)) { | |
| mapping_info.combineWithFollowing(region_info_with_path); | |
| } else { | |
| // print previous mapping info | |
| // avoid printing the empty info at the start | |
| if (mapping_info._size != 0) { | |
| printer.print_single_mapping(region_info, mapping_info); | |
| } | |
| summary.add_mapping(region_info); | |
| mapping_info.process(region_info_with_path); | |
| } | |
| } | |
| assert(region_info.pri_size > 0, "size of region is 0"); | |
| address = region_info.pri_address + region_info.pri_size; | |
| } | |
| printer.print_single_mapping(region_info_with_path.prp_prinfo, mapping_info); | |
| summary.add_mapping(region_info_with_path.prp_prinfo); | |
| st->cr(); | |
| summary.print_on(session); | |
| st->cr(); | |
| } | |
| #endif // __APPLE__ |