8319873: Add windows implementation for jcmd System.map and System.dump_map

Co-authored-by: Simon Tooke <stooke@openjdk.org>
Reviewed-by: stuefe, kevinw, szaldana
This commit is contained in:
Simon Tooke 2024-09-18 09:11:40 +00:00
parent 3895b8fc0b
commit 4ff17c14a5
9 changed files with 322 additions and 16 deletions

View File

@ -0,0 +1,253 @@
/*
* Copyright (c) 2024, 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 "precompiled.hpp"
#include "nmt/memMapPrinter.hpp"
#include "runtime/os.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("file: file mapped, if mapping is not anonymous");
st->print_cr("vm info: VM information (requires NMT)");
{
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();
}

View File

@ -25,7 +25,7 @@
#include "precompiled.hpp"
#ifdef LINUX
#if defined(LINUX) || defined(_WIN64)
#include "gc/shared/collectedHeap.hpp"
#include "logging/logAsyncWriter.hpp"

View File

@ -30,7 +30,7 @@
#include "nmt/memTag.hpp"
#include "utilities/globalDefinitions.hpp"
#ifdef LINUX
#if defined(LINUX) || defined(_WIN64)
class outputStream;
class CachedNMTInformation;

View File

@ -138,9 +138,11 @@ void DCmd::register_dcmds(){
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<PerfMapDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<TrimCLibcHeapDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<MallocInfoDcmd>(full_export, true, false));
#endif // LINUX
#if defined(LINUX) || defined(_WIN64)
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<SystemMapDCmd>(full_export, true,false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<SystemDumpMapDCmd>(full_export, true,false));
#endif // LINUX
#endif // LINUX or WINDOWS
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<CodeHeapAnalyticsDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<CompilerDirectivesPrintDCmd>(full_export, true, false));
@ -1172,7 +1174,7 @@ void CompilationMemoryStatisticDCmd::execute(DCmdSource source, TRAPS) {
CompilationMemoryStatistic::print_all_by_size(output(), human_readable, minsize);
}
#ifdef LINUX
#if defined(LINUX) || defined(_WIN64)
SystemMapDCmd::SystemMapDCmd(outputStream* output, bool heap) : DCmd(output, heap) {}
@ -1190,16 +1192,22 @@ SystemDumpMapDCmd::SystemDumpMapDCmd(outputStream* output, bool heap) :
void SystemDumpMapDCmd::execute(DCmdSource source, TRAPS) {
const char* name = _filename.value();
if (name == nullptr || name[0] == 0) {
output()->print_cr("filename is empty or not specified. No file written");
return;
}
fileStream fs(name);
if (fs.is_open()) {
if (!MemTracker::enabled()) {
output()->print_cr("(NMT is disabled, will not annotate mappings).");
}
MemMapPrinter::print_all_mappings(&fs);
#ifndef _WIN64
// For the readers convenience, resolve path name.
char tmp[JVM_MAXPATHLEN];
const char* absname = os::Posix::realpath(name, tmp, sizeof(tmp));
name = absname != nullptr ? absname : name;
#endif
output()->print_cr("Memory map dumped to \"%s\".", name);
} else {
output()->print_cr("Failed to open \"%s\" for writing (%s).", name, os::strerror(errno));

View File

@ -981,14 +981,14 @@ public:
virtual void execute(DCmdSource source, TRAPS);
};
#ifdef LINUX
#if defined(LINUX) || defined(_WIN64)
class SystemMapDCmd : public DCmd {
public:
SystemMapDCmd(outputStream* output, bool heap);
static const char* name() { return "System.map"; }
static const char* description() {
return "Prints an annotated process memory map of the VM process (linux only).";
return "Prints an annotated process memory map of the VM process (linux and Windows only).";
}
static const char* impact() { return "Medium; can be high for very large java heaps."; }
static const JavaPermission permission() {
@ -1006,7 +1006,7 @@ public:
SystemDumpMapDCmd(outputStream* output, bool heap);
static const char* name() { return "System.dump_map"; }
static const char* description() {
return "Dumps an annotated process memory map to an output file (linux only).";
return "Dumps an annotated process memory map to an output file (linux and Windows only).";
}
static const char* impact() { return "Medium; can be high for very large java heaps."; }
static const JavaPermission permission() {
@ -1017,6 +1017,6 @@ public:
virtual void execute(DCmdSource source, TRAPS);
};
#endif // LINUX
#endif // LINUX or WINDOWS
#endif // SHARE_SERVICES_DIAGNOSTICCOMMAND_HPP

View File

@ -35,7 +35,7 @@ import java.util.regex.Pattern;
* @test
* @summary Test of diagnostic command System.map
* @library /test/lib
* @requires (os.family=="linux")
* @requires (os.family == "linux" | os.family == "windows")
* @modules java.base/jdk.internal.misc
* java.compiler
* java.management
@ -63,11 +63,11 @@ public class SystemDumpMapTest extends SystemMapTestBase {
boolean NMTOff = output.contains("NMT is disabled");
String regexBase = ".*0x\\p{XDigit}+ - 0x\\p{XDigit}+ +\\d+";
HashSet<Pattern> patterns = new HashSet<>();
for (String s: shouldMatchUnconditionally) {
for (String s: shouldMatchUnconditionally()) {
patterns.add(Pattern.compile(s));
}
if (!NMTOff) { // expect VM annotations if NMT is on
for (String s: shouldMatchIfNMTIsEnabled) {
for (String s: shouldMatchIfNMTIsEnabled()) {
patterns.add(Pattern.compile(s));
}
}

View File

@ -31,7 +31,7 @@ import jdk.test.lib.process.OutputAnalyzer;
* @test
* @summary Test of diagnostic command System.map
* @library /test/lib
* @requires (os.family=="linux")
* @requires (os.family == "linux" | os.family == "windows")
* @modules java.base/jdk.internal.misc
* java.compiler
* java.management
@ -42,11 +42,11 @@ public class SystemMapTest extends SystemMapTestBase {
public void run(CommandExecutor executor) {
OutputAnalyzer output = executor.execute("System.map");
boolean NMTOff = output.contains("NMT is disabled");
for (String s: shouldMatchUnconditionally) {
for (String s: shouldMatchUnconditionally()) {
output.shouldMatch(s);
}
if (!NMTOff) { // expect VM annotations if NMT is on
for (String s: shouldMatchIfNMTIsEnabled) {
for (String s: shouldMatchIfNMTIsEnabled()) {
output.shouldMatch(s);
}
}

View File

@ -22,6 +22,8 @@
* questions.
*/
import jdk.test.lib.Platform;
public class SystemMapTestBase {
// e.g.
@ -29,6 +31,7 @@ public class SystemMapTestBase {
private static final String range = "0x\\p{XDigit}+-0x\\p{XDigit}+";
private static final String space = " +";
private static final String someSize = "\\d+";
private static final String someNumber = "(0x\\p{XDigit}+|\\d+)";
private static final String pagesize = "(4K|8K|16K|64K|2M|16M|64M)";
private static final String prot = "[rwsxp-]+";
@ -44,7 +47,8 @@ public class SystemMapTestBase {
// java heap is either committed, non-shared, or - in case of ZGC - committed and shared.
private static final String regexBase_java_heap = regexBase + "(shrd,)?com.*";
protected static final String shouldMatchUnconditionally[] = {
private static final String shouldMatchUnconditionally_linux[] = {
// java launcher
regexBase_committed + "/bin/java",
// libjvm
@ -55,7 +59,7 @@ public class SystemMapTestBase {
regexBase_shared_and_committed + "hsperfdata_.*"
};
protected static final String shouldMatchIfNMTIsEnabled[] = {
private static final String shouldMatchIfNMTIsEnabled_linux[] = {
regexBase_java_heap + "JAVAHEAP.*",
// metaspace
regexBase_committed + "META.*",
@ -66,4 +70,43 @@ public class SystemMapTestBase {
// Main thread stack
regexBase_committed + "STACK.*main.*"
};
// windows:
private static final String winprot = "[\\-rwxcin]*";
private static final String wintype = "[rc]-(img|map|pvt)";
private static final String winbase = range + space + someSize + space + winprot + space;
private static final String winimage = winbase + "c-img" + space + someNumber + space;
private static final String wincommitted = winbase + "c-pvt" + space + someNumber + space;
private static final String winreserved = winbase + "r-pvt" + space + someNumber + space;
private static final String shouldMatchUnconditionally_windows[] = {
// java launcher
winimage + ".*[\\/\\\\]bin[\\/\\\\]java[.]exe",
// libjvm
winimage + ".*[\\/\\\\]bin[\\/\\\\].*[\\/\\\\]jvm.dll"
};
private static final String shouldMatchIfNMTIsEnabled_windows[] = {
wincommitted + "JAVAHEAP.*",
// metaspace
wincommitted + "META.*",
// parts of metaspace should be uncommitted
winreserved + "META.*",
// code cache
wincommitted + "CODE.*",
// Main thread stack
wincommitted + "STACK-\\d+-main.*"
};
private static final boolean isWindows = Platform.isWindows();
protected static String[] shouldMatchUnconditionally() {
return isWindows ? shouldMatchUnconditionally_windows : shouldMatchUnconditionally_linux;
}
protected static String[] shouldMatchIfNMTIsEnabled() {
return isWindows ? shouldMatchIfNMTIsEnabled_windows : shouldMatchIfNMTIsEnabled_linux;
}
}

View File

@ -51,6 +51,8 @@ public class TestJcmdPIDSubstitution {
verifyOutputFilenames("GC.heap_dump", FILENAME);
if (Platform.isLinux()) {
verifyOutputFilenames("Compiler.perfmap", FILENAME);
}
if (Platform.isLinux() || Platform.isWindows()) {
verifyOutputFilenames("System.dump_map", "-F=%s".formatted(FILENAME));
}
}