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 (®ion_info_with_path, sizeof (region_info_with_path));
353+ int retval = proc_pidinfo (pid, PROC_PIDREGIONPATHINFO, (uint64_t )address, ®ion_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__
0 commit comments