Skip to content

Commit 3b9de11

Browse files
Simon Tooketstuefe
Simon Tooke
authored andcommitted
8319875: Add macOS implementation for jcmd System.map
Reviewed-by: stuefe, szaldana
1 parent ebb27c2 commit 3b9de11

9 files changed

+543
-78
lines changed
+383
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
/*
2+
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
3+
* Copyright (c) 2023, 2024, Red Hat, Inc. and/or its affiliates.
4+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5+
*
6+
* This code is free software; you can redistribute it and/or modify it
7+
* under the terms of the GNU General Public License version 2 only, as
8+
* published by the Free Software Foundation.
9+
*
10+
* This code is distributed in the hope that it will be useful, but WITHOUT
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13+
* version 2 for more details (a copy is included in the LICENSE file that
14+
* accompanied this code).
15+
*
16+
* You should have received a copy of the GNU General Public License version
17+
* 2 along with this work; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19+
*
20+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
21+
* or visit www.oracle.com if you need additional information or have any
22+
* questions.
23+
*
24+
*/
25+
26+
#if defined(__APPLE__)
27+
28+
#include "precompiled.hpp"
29+
30+
#include "nmt/memMapPrinter.hpp"
31+
#include "runtime/os.hpp"
32+
#include "utilities/align.hpp"
33+
#include "utilities/globalDefinitions.hpp"
34+
#include "utilities/powerOfTwo.hpp"
35+
36+
#include <limits.h>
37+
#include <stdio.h>
38+
#include <stdlib.h>
39+
#include <libproc.h>
40+
#include <unistd.h>
41+
42+
#include <mach/vm_inherit.h>
43+
#include <mach/vm_prot.h>
44+
#include <mach/mach_vm.h>
45+
46+
// maximum number of mapping records returned
47+
static const int MAX_REGIONS_RETURNED = 1000000;
48+
49+
// ::mmap() on MacOS is a layer on top of Mach system calls, and will allocate in 128MB chunks.
50+
// This code will coalesce a series of identical 128GB chunks (maybe followed by one smaller chunk
51+
// with identical flags) into one.
52+
// Unfortunately, two or more identically allocated contiguous sections will appear as one, if the
53+
// first section is size 128MB. vmmap(1) has the same issue.
54+
static const int MACOS_PARTIAL_ALLOCATION_SIZE = 128 * M;
55+
56+
class MappingInfo {
57+
proc_regioninfo _rinfo;
58+
public:
59+
const char* _address;
60+
size_t _size;
61+
stringStream _share_buffer;
62+
stringStream _type_buffer;
63+
stringStream _protect_buffer;
64+
stringStream _file_name;
65+
const char* _tag_text;
66+
67+
MappingInfo() : _address(nullptr), _size(0), _tag_text(nullptr) {}
68+
69+
void reset() {
70+
_share_buffer.reset();
71+
_protect_buffer.reset();
72+
_type_buffer.reset();
73+
_file_name.reset();
74+
_tag_text = nullptr;
75+
}
76+
77+
bool canCombine(const proc_regionwithpathinfo& mem_info) {
78+
const proc_regioninfo& n = mem_info.prp_prinfo;
79+
bool cc = _rinfo.pri_size == MACOS_PARTIAL_ALLOCATION_SIZE
80+
&& n.pri_address == (_rinfo.pri_address + _size)
81+
&& n.pri_protection == _rinfo.pri_protection
82+
&& n.pri_max_protection == _rinfo.pri_max_protection
83+
&& n.pri_user_tag == _rinfo.pri_user_tag
84+
&& n.pri_share_mode == _rinfo.pri_share_mode
85+
&& n.pri_offset == 0;
86+
return cc;
87+
}
88+
89+
void combineWithFollowing(const proc_regionwithpathinfo& mem_info) {
90+
_size += mem_info.prp_prinfo.pri_size;
91+
}
92+
93+
void process(const proc_regionwithpathinfo& mem_info) {
94+
reset();
95+
96+
_rinfo = mem_info.prp_prinfo;
97+
98+
_address = (const char*) _rinfo.pri_address;
99+
_size = _rinfo.pri_size;
100+
101+
if (mem_info.prp_vip.vip_path[0] != '\0') {
102+
_file_name.print_raw(mem_info.prp_vip.vip_path);
103+
}
104+
// proc_regionfilename() seems to give bad results, so we don't try to use it here.
105+
106+
char prot[4];
107+
char maxprot[4];
108+
rwbits(_rinfo.pri_protection, prot);
109+
rwbits(_rinfo.pri_max_protection, maxprot);
110+
_protect_buffer.print("%s/%s", prot, maxprot);
111+
112+
get_share_mode(_share_buffer, _rinfo);
113+
_tag_text = tagToStr(_rinfo.pri_user_tag);
114+
}
115+
116+
static void get_share_mode(outputStream& out, const proc_regioninfo& rinfo) {
117+
static const char* share_strings[] = {
118+
"cow", "pvt", "---", "shr", "tsh", "p/a", "s/a", "lpg"
119+
};
120+
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
121+
const bool valid_share_mode = rinfo.pri_share_mode >= SM_COW && rinfo.pri_share_mode <= SM_LARGE_PAGE;
122+
if (valid_share_mode) {
123+
int share_mode = rinfo.pri_share_mode;
124+
out.print_raw(share_strings[share_mode - 1]);
125+
} else {
126+
out.print_cr("invalid pri_share_mode (%d)", rinfo.pri_share_mode);
127+
assert(valid_share_mode, "invalid pri_share_mode (%d)", rinfo.pri_share_mode);
128+
}
129+
}
130+
131+
#define X1(TAG, DESCR) X2(TAG, DESCR)
132+
#define X2(TAG, DESCRIPTION) case VM_MEMORY_ ## TAG: return # DESCRIPTION;
133+
static const char* tagToStr(uint32_t user_tag) {
134+
switch (user_tag) {
135+
case 0:
136+
return 0;
137+
X1(MALLOC, malloc);
138+
X1(MALLOC_SMALL, malloc_small);
139+
X1(MALLOC_LARGE, malloc_large);
140+
X1(MALLOC_HUGE, malloc_huge);
141+
X1(SBRK, sbrk);
142+
X1(REALLOC, realloc);
143+
X1(MALLOC_TINY, malloc_tiny);
144+
X1(MALLOC_LARGE_REUSABLE, malloc_large_reusable);
145+
X1(MALLOC_LARGE_REUSED, malloc_lage_reused);
146+
X1(ANALYSIS_TOOL, analysis_tool);
147+
X1(MALLOC_NANO, malloc_nano);
148+
X1(MALLOC_MEDIUM, malloc_medium);
149+
X1(MALLOC_PROB_GUARD, malloc_prob_guard);
150+
X1(MACH_MSG, malloc_msg);
151+
X1(IOKIT, IOKit);
152+
X1(STACK, stack);
153+
X1(GUARD, guard);
154+
X1(SHARED_PMAP, shared_pmap);
155+
X1(DYLIB, dylib);
156+
X1(UNSHARED_PMAP, unshared_pmap);
157+
X2(APPKIT, AppKit);
158+
X2(FOUNDATION, Foundation);
159+
X2(COREGRAPHICS, CoreGraphics);
160+
X2(CORESERVICES, CoreServices); // is also VM_MEMORY_CARBON
161+
X2(JAVA, Java);
162+
X2(COREDATA, CoreData);
163+
X1(COREDATA_OBJECTIDS, CodeData_objectids);
164+
X1(ATS, ats);
165+
X1(DYLD, dyld);
166+
X1(DYLD_MALLOC, dyld_malloc);
167+
X1(SQLITE, sqlite);
168+
X1(JAVASCRIPT_CORE, javascript_core);
169+
X1(JAVASCRIPT_JIT_EXECUTABLE_ALLOCATOR, javascript_jit_executable_allocator);
170+
X1(JAVASCRIPT_JIT_REGISTER_FILE, javascript_jit_register_file);
171+
X1(OPENCL, OpenCL);
172+
X2(COREIMAGE, CoreImage);
173+
X2(IMAGEIO, ImageIO);
174+
X2(COREPROFILE, CoreProfile);
175+
X1(APPLICATION_SPECIFIC_1, application_specific_1);
176+
X1(APPLICATION_SPECIFIC_16, application_specific_16);
177+
X1(OS_ALLOC_ONCE, os_alloc_once);
178+
X1(GENEALOGY, genealogy);
179+
default:
180+
static char buffer[30];
181+
snprintf(buffer, sizeof(buffer), "user_tag=0x%x(%d)", user_tag, user_tag);
182+
return buffer;
183+
}
184+
}
185+
186+
static void rwbits(int rw, char bits[4]) {
187+
bits[0] = rw & VM_PROT_READ ? 'r' : '-';
188+
bits[1] = rw & VM_PROT_WRITE ? 'w' : '-';
189+
bits[2] = rw & VM_PROT_EXECUTE ? 'x' : '-';
190+
bits[3] = 0;
191+
}
192+
};
193+
194+
class ProcSmapsSummary {
195+
unsigned _num_mappings;
196+
size_t _private;
197+
size_t _committed; // combined committed size
198+
size_t _reserved; // reserved but not committed
199+
size_t _shared; // combined shared size
200+
size_t _swapped_out; // combined amount of swapped-out memory
201+
public:
202+
ProcSmapsSummary() : _num_mappings(0), _private(0),
203+
_committed(0), _shared(0), _swapped_out(0) {}
204+
205+
void add_mapping(const proc_regioninfo& region_info) {
206+
_num_mappings++;
207+
208+
bool is_private = region_info.pri_share_mode == SM_PRIVATE
209+
|| region_info.pri_share_mode == SM_PRIVATE_ALIASED;
210+
bool is_shared = region_info.pri_share_mode == SM_SHARED
211+
|| region_info.pri_share_mode == SM_SHARED_ALIASED
212+
|| region_info.pri_share_mode == SM_TRUESHARED
213+
|| region_info.pri_share_mode == SM_COW;
214+
bool is_committed = region_info.pri_share_mode == SM_EMPTY
215+
&& region_info.pri_max_protection == VM_PROT_ALL
216+
&& ((region_info.pri_protection & VM_PROT_DEFAULT) == VM_PROT_DEFAULT);
217+
bool is_reserved = region_info.pri_share_mode == SM_EMPTY
218+
&& region_info.pri_max_protection == VM_PROT_ALL
219+
&& region_info.pri_protection == VM_PROT_NONE;
220+
221+
_private += is_private ? region_info.pri_size : 0;
222+
_shared += is_shared ? region_info.pri_size : 0;
223+
_swapped_out += region_info.pri_pages_swapped_out;
224+
_committed += is_committed ? region_info.pri_size : 0;
225+
_reserved += is_reserved ? region_info.pri_size : 0;
226+
}
227+
228+
void print_on(const MappingPrintSession& session) const {
229+
outputStream* st = session.out();
230+
231+
st->print_cr("Number of mappings: %u", _num_mappings);
232+
233+
task_vm_info vm_info;
234+
mach_msg_type_number_t num_out = TASK_VM_INFO_COUNT;
235+
kern_return_t err = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)(&vm_info), &num_out);
236+
if (err == KERN_SUCCESS) {
237+
st->print_cr(" vsize: %llu (%llu%s)", vm_info.virtual_size, PROPERFMTARGS(vm_info.virtual_size));
238+
st->print_cr(" rss: %llu (%llu%s)", vm_info.resident_size, PROPERFMTARGS(vm_info.resident_size));
239+
st->print_cr(" peak rss: %llu (%llu%s)", vm_info.resident_size_peak, PROPERFMTARGS(vm_info.resident_size_peak));
240+
st->print_cr(" page size: %d (%ld%s)", vm_info.page_size, PROPERFMTARGS((size_t)vm_info.page_size));
241+
} else {
242+
st->print_cr("error getting vm_info %d", err);
243+
}
244+
st->print_cr(" reserved: %zu (" PROPERFMT ")", _reserved, PROPERFMTARGS(_reserved));
245+
st->print_cr(" committed: %zu (" PROPERFMT ")", _committed, PROPERFMTARGS(_committed));
246+
st->print_cr(" private: %zu (" PROPERFMT ")", _private, PROPERFMTARGS(_private));
247+
st->print_cr(" shared: %zu (" PROPERFMT ")", _shared, PROPERFMTARGS(_shared));
248+
st->print_cr(" swapped out: %zu (" PROPERFMT ")", _swapped_out * vm_info.page_size, PROPERFMTARGS(_swapped_out * vm_info.page_size));
249+
}
250+
};
251+
252+
class ProcSmapsPrinter {
253+
const MappingPrintSession& _session;
254+
public:
255+
ProcSmapsPrinter(const MappingPrintSession& session) :
256+
_session(session)
257+
{}
258+
259+
void print_single_mapping(const proc_regioninfo& region_info, const MappingInfo& mapping_info) const {
260+
outputStream* st = _session.out();
261+
#define INDENT_BY(n) \
262+
if (st->fill_to(n) == 0) { \
263+
st->print(" "); \
264+
}
265+
st->print("%#014.12llx-%#014.12llx", (uint64_t)(mapping_info._address), (uint64_t)(mapping_info._address + mapping_info._size));
266+
INDENT_BY(38);
267+
st->print("%12ld", mapping_info._size);
268+
INDENT_BY(51);
269+
st->print("%s", mapping_info._protect_buffer.base());
270+
INDENT_BY(59);
271+
st->print("%s", mapping_info._share_buffer.base());
272+
st->print("%s", mapping_info._type_buffer.base());
273+
INDENT_BY(64);
274+
st->print("%#11llx", region_info.pri_offset);
275+
INDENT_BY(77);
276+
if (_session.print_nmt_info_for_region((const void*)mapping_info._address, (const void*)(mapping_info._address + mapping_info._size))) {
277+
st->print(" ");
278+
} else {
279+
const char* tag = mapping_info._tag_text;
280+
if (tag != nullptr) {
281+
st->print("[%s] ", tag);
282+
}
283+
}
284+
285+
st->print_raw(mapping_info._file_name.base());
286+
st->cr();
287+
288+
#undef INDENT_BY
289+
}
290+
291+
void print_legend() const {
292+
outputStream* st = _session.out();
293+
st->print_cr("from, to, vsize: address range and size");
294+
st->print_cr("prot: protection:");
295+
st->print_cr(" rwx: read / write / execute");
296+
st->print_cr("share: share mode:");
297+
st->print_cr(" cow: copy on write");
298+
st->print_cr(" pvt: private");
299+
st->print_cr(" shr: shared");
300+
st->print_cr(" tsh: true shared");
301+
st->print_cr(" p/a: private aliased");
302+
st->print_cr(" s/a: shared aliased");
303+
st->print_cr(" lpg: large page");
304+
st->print_cr("offset: offset from start of allocation block");
305+
st->print_cr("vminfo: VM information (requires NMT)");
306+
st->print_cr("file: file mapped, if mapping is not anonymous");
307+
{
308+
streamIndentor si(st, 16);
309+
_session.print_nmt_flag_legend();
310+
}
311+
st->print_cr("file: file mapped, if mapping is not anonymous");
312+
}
313+
314+
void print_header() const {
315+
outputStream* st = _session.out();
316+
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7
317+
// 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
318+
// 0x000102890000-0x000102898000 32768 r--/r-- cow 0xc000 /Users/simont/dev/openjdk/jdk/build/macos-aarch64-fastdebug-shenandoah/images/jdk/bin/java
319+
st->print_cr("from to vsize prot share offset vminfo/file");
320+
st->print_cr("==================================================================================================");
321+
}
322+
};
323+
324+
static bool is_interesting(const proc_regionwithpathinfo& info) {
325+
return info.prp_prinfo.pri_share_mode != SM_EMPTY
326+
|| info.prp_prinfo.pri_user_tag != 0
327+
|| info.prp_vip.vip_path[0] != '\0'
328+
|| info.prp_prinfo.pri_protection != 0
329+
|| info.prp_prinfo.pri_max_protection != 0;
330+
}
331+
332+
void MemMapPrinter::pd_print_all_mappings(const MappingPrintSession& session) {
333+
334+
ProcSmapsPrinter printer(session);
335+
ProcSmapsSummary summary;
336+
outputStream* const st = session.out();
337+
const pid_t pid = getpid();
338+
339+
printer.print_legend();
340+
st->cr();
341+
printer.print_header();
342+
343+
proc_regionwithpathinfo region_info_with_path;
344+
MappingInfo mapping_info;
345+
uint64_t address = 0;
346+
int region_count = 0;
347+
while (true) {
348+
if (++region_count > MAX_REGIONS_RETURNED) {
349+
st->print_cr("limit of %d regions reached (results inaccurate)", region_count);
350+
break;
351+
}
352+
::bzero(&region_info_with_path, sizeof(region_info_with_path));
353+
int retval = proc_pidinfo(pid, PROC_PIDREGIONPATHINFO, (uint64_t)address, &region_info_with_path, sizeof(region_info_with_path));
354+
if (retval <= 0) {
355+
break;
356+
} else if (retval < (int)sizeof(region_info_with_path)) {
357+
st->print_cr("proc_pidinfo() returned %d", retval);
358+
assert(false, "proc_pidinfo() returned %d", retval);
359+
}
360+
proc_regioninfo& region_info = region_info_with_path.prp_prinfo;
361+
if (is_interesting(region_info_with_path)) {
362+
if (mapping_info.canCombine(region_info_with_path)) {
363+
mapping_info.combineWithFollowing(region_info_with_path);
364+
} else {
365+
// print previous mapping info
366+
// avoid printing the empty info at the start
367+
if (mapping_info._size != 0) {
368+
printer.print_single_mapping(region_info, mapping_info);
369+
}
370+
summary.add_mapping(region_info);
371+
mapping_info.process(region_info_with_path);
372+
}
373+
}
374+
assert(region_info.pri_size > 0, "size of region is 0");
375+
address = region_info.pri_address + region_info.pri_size;
376+
}
377+
printer.print_single_mapping(region_info_with_path.prp_prinfo, mapping_info);
378+
summary.add_mapping(region_info_with_path.prp_prinfo);
379+
st->cr();
380+
summary.print_on(session);
381+
st->cr();
382+
}
383+
#endif // __APPLE__

src/hotspot/os/windows/memMapPrinter_windows.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,9 @@ class MappingInfoPrinter {
197197
st->print_cr("state: region state and type:");
198198
st->print_cr(" state: committed / reserved");
199199
st->print_cr(" type: image / mapped / private");
200+
st->print_cr("offset: offset from start of allocation block");
201+
st->print_cr("vminfo: VM information (requires NMT)");
200202
st->print_cr("file: file mapped, if mapping is not anonymous");
201-
st->print_cr("vm info: VM information (requires NMT)");
202203
{
203204
streamIndentor si(st, 16);
204205
_session.print_nmt_flag_legend();

0 commit comments

Comments
 (0)