1 /*
2  * %CopyrightBegin%
3  *
4  * Copyright Ericsson AB 1998-2018. All Rights Reserved.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * %CopyrightEnd%
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #endif
24 
25 #include "sys.h"
26 #include "erl_vm.h"
27 #include "global.h"
28 #include "erl_process.h"
29 #include "big.h"
30 #include "bif.h"
31 #include "beam_catches.h"
32 #include "erl_debug.h"
33 #include "erl_map.h"
34 
35 #define WITHIN(ptr, x, y) ((x) <= (ptr) && (ptr) < (y))
36 
37 #define IN_HEAP(p, ptr)                                                 \
38     (WITHIN((ptr), p->heap, p->hend) ||                                 \
39      (OLD_HEAP(p) && WITHIN((ptr), OLD_HEAP(p), OLD_HEND(p))))
40 
41 
42 #ifdef __GNUC__
43 /*
44  * Does not work in Microsoft C. Since this is debugging code that will
45  * hardly be used on Windows, get rid of it unless we have Gnu compiler.
46  */
47 #define PTR_SIZE 2*(int)sizeof(long)
48 
49 static const char dashes[PTR_SIZE+3] = {
50     [0 ... PTR_SIZE+1] = '-'
51 };
52 #endif
53 
54 #if defined(DEBUG) && defined(__GNUC__)
55 
56 /*
57  * This file defines functions for use within a debugger like gdb
58  * and the declarations below is just to make gcc quiet.
59  */
60 
61 void pps(Process*, Eterm*);
62 void ptd(Process*, Eterm);
63 void paranoid_display(fmtfn_t, void*, Process*, Eterm);
64 static int dcount;
65 
66 static int pdisplay1(fmtfn_t to, void *to_arg, Process* p, Eterm obj);
67 
ptd(Process * p,Eterm x)68 void ptd(Process* p, Eterm x)
69 {
70     pdisplay1(ERTS_PRINT_STDERR, NULL, p, x);
71     erts_putc(ERTS_PRINT_STDERR, NULL, '\n');
72 }
73 
74 /*
75  * Paranoid version of display which doesn't crasch as easily if there
76  * are errors in the data structures.
77  */
78 
79 void
paranoid_display(fmtfn_t to,void * to_arg,Process * p,Eterm obj)80 paranoid_display(fmtfn_t to, void *to_arg, Process* p, Eterm obj)
81 {
82     dcount = 100000;
83     pdisplay1(to, to_arg, p, obj);
84 }
85 
86 static int
pdisplay1(fmtfn_t to,void * to_arg,Process * p,Eterm obj)87 pdisplay1(fmtfn_t to, void *to_arg, Process* p, Eterm obj)
88 {
89     int i, k;
90     Eterm* nobj;
91 
92     if (dcount-- <= 0)
93 	return(1);
94 
95     if (is_CP(obj)) {
96 	erts_print(to, to_arg, "<cp/header:%0*lX",PTR_SIZE,obj);
97 	return 0;
98     }
99 
100     switch (tag_val_def(obj)) {
101     case NIL_DEF:
102 	erts_print(to, to_arg, "[]");
103 	break;
104     case ATOM_DEF:
105 	erts_print(to, to_arg, "%T", obj);
106 	break;
107     case SMALL_DEF:
108 	erts_print(to, to_arg, "%ld", signed_val(obj));
109 	break;
110 
111     case BIG_DEF:
112 	nobj = big_val(obj);
113 	if (!IN_HEAP(p, nobj)) {
114 	    erts_print(to, to_arg, "#<bad big %X>#", obj);
115 	    return 1;
116 	}
117 
118 	i = BIG_SIZE(nobj);
119 	if (BIG_SIGN(nobj))
120 	    erts_print(to, to_arg, "-#integer(%d) = {", i);
121 	else
122 	    erts_print(to, to_arg, "#integer(%d) = {", i);
123 	erts_print(to, to_arg, "%d", BIG_DIGIT(nobj, 0));
124 	for (k = 1; k < i; k++)
125 	    erts_print(to, to_arg, ",%d", BIG_DIGIT(nobj, k));
126 	erts_putc(to, to_arg, '}');
127 	break;
128     case REF_DEF:
129     case EXTERNAL_REF_DEF: {
130 	Uint32 *ref_num;
131 	erts_print(to, to_arg, "#Ref<%lu", ref_channel_no(obj));
132 	ref_num = ref_numbers(obj);
133 	for (i = ref_no_numbers(obj)-1; i >= 0; i--)
134 	    erts_print(to, to_arg, ",%lu", ref_num[i]);
135 	erts_print(to, to_arg, ">");
136 	break;
137     }
138     case PID_DEF:
139     case EXTERNAL_PID_DEF:
140 	erts_print(to, to_arg, "<%lu.%lu.%lu>",
141 		   pid_channel_no(obj),
142 		   pid_number(obj),
143 		   pid_serial(obj));
144 	break;
145     case PORT_DEF:
146     case EXTERNAL_PORT_DEF:
147 	erts_print(to, to_arg, "#Port<%lu.%lu>",
148 		   port_channel_no(obj),
149 		   port_number(obj));
150 	break;
151     case LIST_DEF:
152 	erts_putc(to, to_arg, '[');
153 	nobj = list_val(obj);
154 	while (1) {
155 	    if (!IN_HEAP(p, nobj)) {
156 		erts_print(to, to_arg, "#<bad list %X>", obj);
157 		return 1;
158 	    }
159 	    if (pdisplay1(to, to_arg, p, *nobj++) != 0)
160 		return(1);
161 	    if (is_not_list(*nobj))
162 		break;
163 	    erts_putc(to, to_arg, ',');
164 	    nobj = list_val(*nobj);
165 	}
166 	if (is_not_nil(*nobj)) {
167 	    erts_putc(to, to_arg, '|');
168 	    if (pdisplay1(to, to_arg, p, *nobj) != 0)
169 		return(1);
170 	}
171 	erts_putc(to, to_arg, ']');
172 	break;
173     case TUPLE_DEF:
174 	nobj = tuple_val(obj);	/* pointer to arity */
175 	i = arityval(*nobj);	/* arity */
176 	erts_putc(to, to_arg, '{');
177 	while (i--) {
178 	    if (pdisplay1(to, to_arg, p, *++nobj) != 0) return(1);
179 	    if (i >= 1) erts_putc(to, to_arg, ',');
180 	}
181 	erts_putc(to, to_arg, '}');
182 	break;
183     case FLOAT_DEF: {
184 	    FloatDef ff;
185 	    GET_DOUBLE(obj, ff);
186 	    erts_print(to, to_arg, "%.20e", ff.fd);
187 	}
188 	break;
189     case BINARY_DEF:
190 	erts_print(to, to_arg, "#Bin");
191 	break;
192     case MATCHSTATE_DEF:
193         erts_print(to, to_arg, "#Matchstate");
194         break;
195     default:
196 	erts_print(to, to_arg, "unknown object %x", obj);
197     }
198     return(0);
199 }
200 
201 void
pps(Process * p,Eterm * stop)202 pps(Process* p, Eterm* stop)
203 {
204     fmtfn_t to = ERTS_PRINT_STDOUT;
205     void *to_arg = NULL;
206     Eterm* sp = STACK_START(p) - 1;
207 
208     if (stop <= STACK_END(p)) {
209         stop = STACK_END(p) + 1;
210     }
211 
212     while(sp >= stop) {
213 	erts_print(to, to_arg, "%0*lx: ", PTR_SIZE, (UWord) sp);
214 	if (is_catch(*sp)) {
215 	    erts_print(to, to_arg, "catch %ld", (UWord)catch_pc(*sp));
216 	} else {
217 	    paranoid_display(to, to_arg, p, *sp);
218 	}
219 	erts_putc(to, to_arg, '\n');
220 	sp--;
221     }
222 }
223 
224 #endif /* DEBUG */
225 
226 static int verify_eterm(Process *p,Eterm element);
verify_eterm(Process * p,Eterm element)227 static int verify_eterm(Process *p,Eterm element)
228 {
229     Eterm *ptr;
230     ErlHeapFragment* mbuf;
231 
232     switch (primary_tag(element)) {
233         case TAG_PRIMARY_LIST: ptr = list_val(element); break;
234         case TAG_PRIMARY_BOXED: ptr = boxed_val(element); break;
235         default: /* Immediate or header/cp */ return 1;
236     }
237 
238     if (p) {
239         if (IN_HEAP(p, ptr))
240             return 1;
241 
242         for (mbuf = p->mbuf; mbuf; mbuf = mbuf->next) {
243             if (WITHIN(ptr, &mbuf->mem[0], &mbuf->mem[0] + mbuf->used_size)) {
244                 return 1;
245             }
246         }
247     }
248     return 0;
249 }
250 
erts_check_stack(Process * p)251 void erts_check_stack(Process *p)
252 {
253     Eterm *elemp;
254     Eterm *stack_start = p->heap + p->heap_sz;
255     Eterm *stack_end = p->htop;
256 
257     if (p->stop > stack_start)
258 	erts_exit(ERTS_ERROR_EXIT,
259 		 "<%lu.%lu.%lu>: Stack underflow\n",
260 		 internal_pid_channel_no(p->common.id),
261 		 internal_pid_number(p->common.id),
262 		 internal_pid_serial(p->common.id));
263 
264     if (p->stop < stack_end)
265 	erts_exit(ERTS_ERROR_EXIT,
266 		 "<%lu.%lu.%lu>: Stack overflow\n",
267 		 internal_pid_channel_no(p->common.id),
268 		 internal_pid_number(p->common.id),
269 		 internal_pid_serial(p->common.id));
270 
271     for (elemp = p->stop; elemp < stack_start; elemp++) {
272 	int in_mbuf = 0;
273 	Eterm *ptr;
274 	ErlHeapFragment* mbuf;
275 	switch (primary_tag(*elemp)) {
276 	case TAG_PRIMARY_LIST: ptr = list_val(*elemp); break;
277 	case TAG_PRIMARY_BOXED: ptr = boxed_val(*elemp); break;
278 	default: /* Immediate or cp */ continue;
279 	}
280 	if (IN_HEAP(p, ptr))
281 	    continue;
282 	for (mbuf = p->mbuf; mbuf; mbuf = mbuf->next)
283 	    if (WITHIN(ptr, &mbuf->mem[0], &mbuf->mem[0] + mbuf->used_size)) {
284 		in_mbuf = 1;
285 		break;
286 	    }
287 	if (in_mbuf)
288 	    continue;
289 
290 	erts_exit(ERTS_ERROR_EXIT,
291 		 "<%lu.%lu.%lu>: Wild stack pointer\n",
292 		 internal_pid_channel_no(p->common.id),
293 		 internal_pid_number(p->common.id),
294 		 internal_pid_serial(p->common.id));
295     }
296 
297 }
298 
299 #if defined(CHECK_FOR_HOLES)
300 static void check_memory(Eterm *start, Eterm *end);
301 
erts_check_for_holes(Process * p)302 void erts_check_for_holes(Process* p)
303 {
304     ErlHeapFragment* hf;
305     Eterm* start;
306 
307     if (p->flags & F_DISABLE_GC)
308 	return;
309 
310     start = p->last_htop ? p->last_htop : HEAP_START(p);
311     check_memory(start, HEAP_TOP(p));
312     p->last_htop = HEAP_TOP(p);
313 
314     for (hf = MBUF(p); hf != 0; hf = hf->next) {
315 	if (hf == p->heap_hfrag)
316 	    continue;
317 	if (hf == p->last_mbuf) {
318 	    break;
319 	}
320 	check_memory(hf->mem, hf->mem+hf->used_size);
321     }
322     p->last_mbuf = MBUF(p);
323 }
324 
check_memory(Eterm * start,Eterm * end)325 static void check_memory(Eterm *start, Eterm *end)
326 {
327     Eterm *pos = start;
328 
329     while (pos < end) {
330         Eterm hval = *pos++;
331 
332         if (hval == ERTS_HOLE_MARKER) {
333             erts_fprintf(stderr,"%s, line %d: ERTS_HOLE_MARKER found at 0x%0*lx\n",
334                          __FILE__, __LINE__,PTR_SIZE,(unsigned long)(pos-1));
335             print_untagged_memory(start,end); /* DEBUGSTUFF */
336 	    abort();
337 	} else if (is_thing(hval)) {
338 	    pos += (thing_arityval(hval));
339 	}
340     }
341 }
342 #endif
343 
344 #ifdef __GNUC__
345 
346 /*
347  * erts_check_heap and erts_check_memory will run through the heap
348  * silently if everything is ok.  If there are strange (untagged) data
349  * in the heap or wild pointers, the system will be halted with an
350  * error message.
351  */
erts_check_heap(Process * p)352 void erts_check_heap(Process *p)
353 {
354     ErlHeapFragment* bp = MBUF(p);
355 
356     erts_check_memory(p,HEAP_START(p),HEAP_TOP(p));
357     if (OLD_HEAP(p) != NULL) {
358         erts_check_memory(p,OLD_HEAP(p),OLD_HTOP(p));
359     }
360 
361     while (bp) {
362 	erts_check_memory(p,bp->mem,bp->mem + bp->used_size);
363         bp = bp->next;
364     }
365 }
366 
erts_check_memory(Process * p,Eterm * start,Eterm * end)367 void erts_check_memory(Process *p, Eterm *start, Eterm *end)
368 {
369     Eterm *pos = start;
370 
371     while (pos < end) {
372         Eterm hval = *pos++;
373 
374 #ifdef DEBUG
375         if (hval == DEBUG_BAD_WORD) {
376             print_untagged_memory(start, end);
377             erts_exit(ERTS_ERROR_EXIT, "Uninitialized HAlloc'ed memory found @ 0x%0*lx!\n",
378                      PTR_SIZE,(unsigned long)(pos - 1));
379         }
380 #endif
381 
382         if (is_thing(hval)) {
383             pos += thing_arityval(hval);
384             continue;
385         }
386 
387         if (verify_eterm(p,hval))
388             continue;
389 
390         erts_exit(ERTS_ERROR_EXIT, "Wild pointer found @ 0x%0*lx!\n",
391                  PTR_SIZE,(unsigned long)(pos - 1));
392     }
393 }
394 
verify_process(Process * p)395 void verify_process(Process *p)
396 {
397 #define VERIFY_AREA(name,ptr,sz) {                                      \
398     int n = (sz);							\
399     while (n--) if(!verify_eterm(p,*(ptr+n)))				\
400         erts_exit(ERTS_ERROR_EXIT,"Wild pointer found in " name " of %T!\n",p->common.id); }
401 
402 #define VERIFY_ETERM(name,eterm) {                                      \
403     if(!verify_eterm(p,eterm))                                          \
404         erts_exit(ERTS_ERROR_EXIT,"Wild pointer found in " name " of %T!\n",p->common.id); }
405 
406 
407     VERBOSE(DEBUG_MEMORY,("Verify process: %T...\n",p->common.id));
408 
409     ERTS_FOREACH_SIG_PRIVQS(
410         p, mp,
411         {
412             if (ERTS_SIG_IS_MSG(mp)) {
413                 VERIFY_ETERM("message term",ERL_MESSAGE_TERM(mp));
414                 VERIFY_ETERM("message token",ERL_MESSAGE_TOKEN(mp));
415             }
416         });
417 
418     erts_check_stack(p);
419     erts_check_heap(p);
420 
421     if (p->dictionary)
422         VERIFY_AREA("dictionary", ERTS_PD_START(p->dictionary), ERTS_PD_SIZE(p->dictionary));
423     VERIFY_ETERM("seq trace token",p->seq_trace_token);
424     VERIFY_ETERM("group leader",p->group_leader);
425     VERIFY_ETERM("fvalue",p->fvalue);
426     VERIFY_ETERM("ftrace",p->ftrace);
427 
428     VERBOSE(DEBUG_MEMORY,("...done\n"));
429 
430 #undef VERIFY_AREA
431 #undef VERIFY_ETERM
432 }
433 
434 /*
435  * print_untagged_memory will print the contents of given memory area.
436  */
print_untagged_memory(Eterm * pos,Eterm * end)437 void print_untagged_memory(Eterm *pos, Eterm *end)
438 {
439     int i = 0;
440     erts_printf("| %*s   | Range: 0x%0*lx - 0x%0*lx%*s|\n",
441                 PTR_SIZE, "",
442                 PTR_SIZE,(unsigned long)pos,
443                 PTR_SIZE,(unsigned long)(end - 1),2 * PTR_SIZE - 2,"");
444     erts_printf("| %-*s | %-*s |\n",PTR_SIZE+2,"Address",
445                 4*PTR_SIZE+11,"Contents");
446     erts_printf("|-%s-|-%s-%s-%s-%s-|\n",dashes,dashes,dashes,dashes,dashes);
447     while( pos < end ) {
448         if (i == 0)
449             erts_printf("| 0x%0*lx | ", PTR_SIZE, (unsigned long)pos);
450         erts_printf("0x%0*lx ",PTR_SIZE,(unsigned long)*pos);
451         pos++; i++;
452         if (i == 4) {
453             erts_printf("|\n");
454             i = 0;
455         }
456     }
457     while (i && i < 4) {
458         erts_printf("%*s",PTR_SIZE+3,"");
459         i++;
460     }
461     if (i != 0)
462         erts_printf("|\n");
463     erts_printf("+-%s-+-%s-%s-%s-%s-+\n",dashes,dashes,dashes,dashes,dashes);
464 }
465 
466 /*
467  * print_tagged_memory will print contents of given memory area and
468  * display it as if it was tagged Erlang terms (which it hopefully
469  * is).  This function knows about forwarding pointers to be able to
470  * print a heap during garbage collection. erts_printf("%T",val)
471  * do not know about forwarding pointers though, so it will still
472  * crash if they are encoutered...
473  */
print_tagged_memory(Eterm * pos,Eterm * end)474 void print_tagged_memory(Eterm *pos, Eterm *end)
475 {
476     erts_printf("+-%s-+-%s-+\n",dashes,dashes);
477     erts_printf("| 0x%0*lx - 0x%0*lx |\n",
478                 PTR_SIZE,(unsigned long)pos,
479                 PTR_SIZE,(unsigned long)(end - 1));
480     erts_printf("| %-*s   | %-*s   |\n",PTR_SIZE,"Address",PTR_SIZE,"Contents");
481     erts_printf("|-%s-|-%s-|\n",dashes,dashes);
482     while( pos < end ) {
483 	Eterm val = pos[0];
484 	erts_printf("| 0x%0*lx | 0x%0*lx | ",
485                     PTR_SIZE,(unsigned long)pos, PTR_SIZE,(unsigned long)val);
486 	++pos;
487         if( is_arity_value(val) ) {
488 	    erts_printf("Arity(%lu)", arityval(val));
489 	} else if( is_thing(val) ) {
490 	    unsigned int ari = thing_arityval(val);
491 	    erts_printf("Thing Arity(%u) Tag(%lu)", ari, thing_subtag(val));
492 	    while( ari ) {
493 		erts_printf("\n| 0x%0*lx | 0x%0*lx | THING",
494                             PTR_SIZE, (unsigned long)pos,
495                             PTR_SIZE, (unsigned long)*pos);
496 		++pos;
497 		--ari;
498 	    }
499 	} else {
500             switch (primary_tag(val)) {
501             case TAG_PRIMARY_BOXED:
502                 if (!is_header(*boxed_val(val))) {
503                     erts_printf("Moved -> 0x%0*lx\n",PTR_SIZE,
504                                 (unsigned long)*boxed_val(val));
505                     continue;
506                 }
507                 break;
508 
509             case TAG_PRIMARY_LIST:
510                 if (is_non_value(*list_val(val))) {
511                     erts_printf("Moved -> 0x%0*lx\n",PTR_SIZE,
512                                 (unsigned long)*(list_val(val) + 1));
513                     continue;
514                 }
515                 break;
516             }
517             erts_printf("%.30T", val);
518         }
519 	erts_printf("\n");
520     }
521     erts_printf("+-%s-+-%s-+\n",dashes,dashes);
522 }
523 
524 static void print_process_memory(Process *p);
print_process_memory(Process * p)525 static void print_process_memory(Process *p)
526 {
527     ErlHeapFragment* bp = MBUF(p);
528 
529     erts_printf("==============================\n");
530     erts_printf("|| Memory info for %T ||\n",p->common.id);
531     erts_printf("==============================\n");
532 
533     erts_printf("-- %-*s ---%s-%s-%s-%s--\n",
534                 PTR_SIZE, "PCB", dashes, dashes, dashes, dashes);
535 
536 
537     erts_printf("  Message Queue:\n");
538     ERTS_FOREACH_SIG_PRIVQS(
539         p, mp,
540         {
541             if (ERTS_SIG_IS_MSG(mp))
542                 erts_printf("| 0x%0*lx | 0x%0*lx |\n",PTR_SIZE,
543                             ERL_MESSAGE_TERM(mp),PTR_SIZE,ERL_MESSAGE_TOKEN(mp));
544         });
545 
546     if (p->dictionary != NULL) {
547         int n = ERTS_PD_SIZE(p->dictionary);
548         Eterm *ptr = ERTS_PD_START(p->dictionary);
549         erts_printf("  Dictionary: ");
550         while (n--) erts_printf("0x%0*lx ",PTR_SIZE,(unsigned long)ptr++);
551         erts_printf("\n");
552     }
553 
554     if (p->arity > 0) {
555         int n = p->arity;
556         Eterm *ptr = p->arg_reg;
557         erts_printf("  Argument Registers: ");
558         while (n--) erts_printf("0x%0*lx ",PTR_SIZE,(unsigned long)*ptr++);
559         erts_printf("\n");
560     }
561 
562     erts_printf("  Trace Token: 0x%0*lx\n",PTR_SIZE,p->seq_trace_token);
563     erts_printf("  Group Leader: 0x%0*lx\n",PTR_SIZE,p->group_leader);
564     erts_printf("  Fvalue: 0x%0*lx\n",PTR_SIZE,p->fvalue);
565     erts_printf("  Ftrace: 0x%0*lx\n",PTR_SIZE,p->ftrace);
566 
567     erts_printf("+- %-*s -+ 0x%0*lx 0x%0*lx %s-%s-+\n",
568                 PTR_SIZE, "Stack",
569                 PTR_SIZE, (unsigned long)STACK_TOP(p),
570                 PTR_SIZE, (unsigned long)STACK_START(p),
571                 dashes, dashes);
572     print_untagged_memory(STACK_TOP(p),STACK_START(p));
573 
574     erts_printf("+- %-*s -+ 0x%0*lx 0x%0*lx 0x%0*lx 0x%0*lx +\n",
575                 PTR_SIZE, "Heap",
576                 PTR_SIZE, (unsigned long)HEAP_START(p),
577                 PTR_SIZE, (unsigned long)HIGH_WATER(p),
578                 PTR_SIZE, (unsigned long)HEAP_TOP(p),
579                 PTR_SIZE, (unsigned long)HEAP_END(p));
580     print_untagged_memory(HEAP_START(p),HEAP_TOP(p));
581 
582     if (OLD_HEAP(p)) {
583         erts_printf("+- %-*s -+ 0x%0*lx 0x%0*lx 0x%0*lx %s-+\n",
584                     PTR_SIZE, "Old Heap",
585                     PTR_SIZE, (unsigned long)OLD_HEAP(p),
586                     PTR_SIZE, (unsigned long)OLD_HTOP(p),
587                     PTR_SIZE, (unsigned long)OLD_HEND(p),
588                     dashes);
589         print_untagged_memory(OLD_HEAP(p),OLD_HTOP(p));
590     }
591 
592     if (bp)
593         erts_printf("+- %-*s -+-%s-%s-%s-%s-+\n",
594                     PTR_SIZE, "heap fragments",
595                     dashes, dashes, dashes, dashes);
596     while (bp) {
597 	print_untagged_memory(bp->mem,bp->mem + bp->used_size);
598         bp = bp->next;
599     }
600 }
601 
602 
print_memory(Process * p)603 void print_memory(Process *p)
604 {
605     if (p != NULL) {
606         print_process_memory(p);
607     }
608 }
609 
print_memory_info(Process * p)610 void print_memory_info(Process *p)
611 {
612     if (p != NULL) {
613         erts_printf("======================================\n");
614         erts_printf("|| Memory info for %-12T ||\n",p->common.id);
615         erts_printf("======================================\n");
616         erts_printf("+- local heap ----%s-%s-%s-%s-+\n",
617                     dashes,dashes,dashes,dashes);
618         erts_printf("| Young | 0x%0*lx - (0x%0*lx) - 0x%0*lx - 0x%0*lx |\n",
619                     PTR_SIZE, (unsigned long)HEAP_START(p),
620                     PTR_SIZE, (unsigned long)HIGH_WATER(p),
621                     PTR_SIZE, (unsigned long)HEAP_TOP(p),
622                     PTR_SIZE, (unsigned long)HEAP_END(p));
623         if (OLD_HEAP(p) != NULL)
624             erts_printf("| Old   | 0x%0*lx - 0x%0*lx - 0x%0*lx   %*s     |\n",
625                         PTR_SIZE, (unsigned long)OLD_HEAP(p),
626                         PTR_SIZE, (unsigned long)OLD_HTOP(p),
627                         PTR_SIZE, (unsigned long)OLD_HEND(p),
628                         PTR_SIZE, "");
629     } else {
630         erts_printf("=================\n");
631         erts_printf("|| Memory info ||\n");
632         erts_printf("=================\n");
633     }
634     erts_printf("+-----------------%s-%s-%s-%s-+\n",dashes,dashes,dashes,dashes);
635 }
636 #endif
637