| /* | |
| * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * Copyright (c) 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. | |
| * | |
| */ | |
| #include "nmt/memMapPrinter.hpp" | |
| #include "os_windows.hpp" | |
| #include "runtime/vm_version.hpp" | |
| #include "utilities/ostream.hpp" | |
| #include <limits.h> | |
| #include <winnt.h> | |
| #include <memoryapi.h> | |
| #include <psapi.h> | |
| /* maximum number of mapping records returned */ | |
| static const int MAX_REGIONS_RETURNED = 1000000; | |
| class MappingInfo { | |
| public: | |
| stringStream _ap_buffer; | |
| stringStream _state_buffer; | |
| stringStream _protect_buffer; | |
| stringStream _type_buffer; | |
| char _file_name[MAX_PATH]; | |
| MappingInfo() {} | |
| void process(MEMORY_BASIC_INFORMATION& mem_info) { | |
| _ap_buffer.reset(); | |
| _state_buffer.reset(); | |
| _protect_buffer.reset(); | |
| _type_buffer.reset(); | |
| get_protect_string(_ap_buffer, mem_info.AllocationProtect); | |
| get_state_string(_state_buffer, mem_info); | |
| get_protect_string(_protect_buffer, mem_info.Protect); | |
| get_type_string(_type_buffer, mem_info); | |
| _file_name[0] = 0; | |
| if (mem_info.Type == MEM_IMAGE) { | |
| HMODULE hModule = 0; | |
| if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast<LPCSTR>(mem_info.AllocationBase), &hModule)) { | |
| GetModuleFileName(hModule, _file_name, sizeof(_file_name)); | |
| } | |
| } | |
| } | |
| void get_protect_string(outputStream& out, DWORD prot) { | |
| const char read_c = prot & (PAGE_READONLY | PAGE_READWRITE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_WRITECOPY) ? 'r' : '-'; | |
| const char write_c = prot & (PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY) ? 'w' : '-'; | |
| const char execute_c = prot & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY) ? 'x' : '-'; | |
| out.print("%c%c%c", read_c, write_c, execute_c); | |
| if (prot & (PAGE_WRITECOPY | PAGE_EXECUTE_WRITECOPY)) { | |
| out.put('c'); | |
| } | |
| if (prot & PAGE_GUARD) { | |
| out.put('g'); | |
| } | |
| if (prot & PAGE_NOCACHE) { | |
| out.put('n'); | |
| } | |
| if (prot & PAGE_WRITECOMBINE) { | |
| out.put('W'); | |
| } | |
| const DWORD bits = PAGE_NOACCESS | PAGE_READONLY | PAGE_READWRITE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | |
| | PAGE_WRITECOPY | PAGE_EXECUTE_WRITECOPY | PAGE_EXECUTE | |
| | PAGE_GUARD | PAGE_NOCACHE | PAGE_WRITECOMBINE; | |
| if ((prot & bits) != prot) { | |
| out.print_cr("Unknown Windows memory protection value: 0x%x unknown bits: 0x%x", prot, prot & ~bits); | |
| assert(false, "Unknown Windows memory protection value: 0x%x unknown bits: 0x%x", prot, prot & ~bits); | |
| } | |
| } | |
| void get_state_string(outputStream& out, MEMORY_BASIC_INFORMATION& mem_info) { | |
| if (mem_info.State == MEM_COMMIT) { | |
| out.put('c'); | |
| } else if (mem_info.State == MEM_FREE) { | |
| out.put('f'); | |
| } else if (mem_info.State == MEM_RESERVE) { | |
| out.put('r'); | |
| } else { | |
| out.print_cr("Unknown Windows memory state value: 0x%x", mem_info.State); | |
| assert(false, "Unknown Windows memory state value: 0x%x", mem_info.State); | |
| } | |
| } | |
| void get_type_string(outputStream& out, MEMORY_BASIC_INFORMATION& mem_info) { | |
| if (mem_info.Type == MEM_IMAGE) { | |
| out.print("img"); | |
| } else if (mem_info.Type == MEM_MAPPED) { | |
| out.print("map"); | |
| } else if (mem_info.Type == MEM_PRIVATE) { | |
| out.print("pvt"); | |
| } else if (mem_info.Type == 0 && mem_info.State == MEM_FREE) { | |
| out.print("---"); | |
| } else { | |
| out.print_cr("Unknown Windows memory type 0x%x", mem_info.Type); | |
| assert(false, "Unknown Windows memory type 0x%x", mem_info.Type); | |
| } | |
| } | |
| }; | |
| class MappingInfoSummary { | |
| unsigned _num_mappings; | |
| size_t _total_region_size; // combined resident set size | |
| size_t _total_committed; // combined committed size | |
| class WinOsInfo : public os::win32 { | |
| public: | |
| static void printOsInfo(outputStream* st) { | |
| st->print("OS:"); | |
| os::win32::print_windows_version(st); | |
| os::win32::print_uptime_info(st); | |
| VM_Version::print_platform_virtualization_info(st); | |
| os::print_memory_info(st); | |
| } | |
| }; | |
| public: | |
| MappingInfoSummary() : _num_mappings(0), _total_region_size(0), | |
| _total_committed(0) {} | |
| void add_mapping(const MEMORY_BASIC_INFORMATION& mem_info, const MappingInfo& mapping_info) { | |
| if (mem_info.State != MEM_FREE) { | |
| _num_mappings++; | |
| _total_region_size += mem_info.RegionSize; | |
| _total_committed += mem_info.State == MEM_COMMIT ? mem_info.RegionSize : 0; | |
| } | |
| } | |
| void print_on(const MappingPrintSession& session) const { | |
| outputStream* st = session.out(); | |
| WinOsInfo::printOsInfo(st); | |
| st->print_cr("current process reserved memory: " PROPERFMT, PROPERFMTARGS(_total_region_size)); | |
| st->print_cr("current process committed memory: " PROPERFMT, PROPERFMTARGS(_total_committed)); | |
| st->print_cr("current process region count: " PROPERFMT, PROPERFMTARGS(_num_mappings)); | |
| } | |
| }; | |
| class MappingInfoPrinter { | |
| const MappingPrintSession& _session; | |
| public: | |
| MappingInfoPrinter(const MappingPrintSession& session) : | |
| _session(session) | |
| {} | |
| void print_single_mapping(const MEMORY_BASIC_INFORMATION& mem_info, const MappingInfo& mapping_info) const { | |
| outputStream* st = _session.out(); | |
| #define INDENT_BY(n) \ | |
| if (st->fill_to(n) == 0) { \ | |
| st->print(" "); \ | |
| } | |
| st->print(PTR_FORMAT "-" PTR_FORMAT, mem_info.BaseAddress, static_cast<const char*>(mem_info.BaseAddress) + mem_info.RegionSize); | |
| INDENT_BY(38); | |
| st->print("%12zu", mem_info.RegionSize); | |
| INDENT_BY(51); | |
| st->print("%s", mapping_info._protect_buffer.base()); | |
| INDENT_BY(57); | |
| st->print("%s-%s", mapping_info._state_buffer.base(), mapping_info._type_buffer.base()); | |
| INDENT_BY(63); | |
| st->print("%#11llx", reinterpret_cast<const unsigned long long>(mem_info.BaseAddress) - reinterpret_cast<const unsigned long long>(mem_info.AllocationBase)); | |
| INDENT_BY(72); | |
| if (_session.print_nmt_info_for_region(mem_info.BaseAddress, static_cast<const char*>(mem_info.BaseAddress) + mem_info.RegionSize)) { | |
| st->print(" "); | |
| } | |
| st->print_raw(mapping_info._file_name); | |
| #undef INDENT_BY | |
| st->cr(); | |
| } | |
| 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(" c: copy on write"); | |
| st->print_cr(" g: guard"); | |
| st->print_cr(" n: no cache"); | |
| st->print_cr(" W: write combine"); | |
| st->print_cr("state: region state and type:"); | |
| st->print_cr(" state: committed / reserved"); | |
| st->print_cr(" type: image / mapped / private"); | |
| 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(); | |
| } | |
| } | |
| void print_header() const { | |
| outputStream* st = _session.out(); | |
| // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 | |
| // 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 | |
| // 0x00007ffb24565000-0x00007ffb24a7e000 5345280 r-- c-img 0x1155000 C:\work\jdk\build\fastdebug\jdk\bin\server\jvm.dll | |
| st->print_cr("from to vsize prot state offset vminfo/file"); | |
| st->print_cr("==========================================================================================="); | |
| } | |
| }; | |
| void MemMapPrinter::pd_print_all_mappings(const MappingPrintSession& session) { | |
| HANDLE hProcess = GetCurrentProcess(); | |
| MappingInfoPrinter printer(session); | |
| MappingInfoSummary summary; | |
| outputStream* const st = session.out(); | |
| printer.print_legend(); | |
| st->cr(); | |
| printer.print_header(); | |
| MEMORY_BASIC_INFORMATION mem_info; | |
| MappingInfo mapping_info; | |
| int region_count = 0; | |
| ::memset(&mem_info, 0, sizeof(mem_info)); | |
| for (char* ptr = 0; VirtualQueryEx(hProcess, ptr, &mem_info, sizeof(mem_info)) == sizeof(mem_info); ) { | |
| assert(mem_info.RegionSize > 0, "RegionSize is not greater than zero"); | |
| if (++region_count > MAX_REGIONS_RETURNED) { | |
| st->print_cr("limit of %d regions reached (results inaccurate)", region_count); | |
| break; | |
| } | |
| mapping_info.process(mem_info); | |
| if (mem_info.State != MEM_FREE) { | |
| printer.print_single_mapping(mem_info, mapping_info); | |
| summary.add_mapping(mem_info, mapping_info); | |
| } | |
| ptr += mem_info.RegionSize; | |
| ::memset(&mem_info, 0, sizeof(mem_info)); | |
| } | |
| st->cr(); | |
| summary.print_on(session); | |
| st->cr(); | |
| } |