xref: /illumos-gate/usr/src/cmd/ptools/pmadvise/pmadvise.c (revision e00bdde3c6d406f40f53f3025defadc22f7ec31a)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * Copyright (c) 2015, Joyent, Inc. All rights reserved.
29  */
30 
31 /*
32  * pmadvise
33  *
34  * ptool wrapper for madvise(3C) to apply memory advice to running processes
35  *
36  * usage:	pmadvise -o option[,option] [-v] [-F] pid ...
37  *  (Give "advice" about a process's memory)
38  *  -o option[,option]: options are
39  *      private=<advice>
40  *      shared=<advice>
41  *      heap=<advice>
42  *      stack=<advice>
43  *      <segaddr>[:<length>]=<advice>
44  *     valid <advice> is one of:
45  *      normal, random, sequential, willneed, dontneed,
46  *      free, access_lwp, access_many, access_default
47  *  -v: verbose output
48  *  -F: force grabbing of the target process(es)
49  *  -l: show unresolved dynamic linker map names
50  *  pid: process id list
51  *
52  *
53  * Advice passed to this tool are organized into various lists described here:
54  *  rawadv_list: includes all specific advice from command line (specific
55  *               advice being those given to a particular address range rather
56  *               than a type like "heap" or "stack".  In contrast, these
57  *               types are referred to as generic advice). Duplicates allowed.
58  *               List ordered by addr, then by size (largest size first).
59  *               Created once per run.
60  *  merged_list: includes all specific advice from the rawadv_list as well as
61  *               all generic advice.  This must be recreated for each process
62  *               as the generic advice will apply to different regions for
63  *               different processes. Duplicates allowed. List ordered by addr,
64  *               then by size (largest size first). Created once per pid.
65  *  chopped_list: used for verbose output only. This list parses the merged
66  *                list such that it eliminates any overlap and combines the
67  *                advice. Easiest to think of this visually: if you take all
68  *                the advice in the merged list and lay them down on a memory
69  *                range of the entire process (laying on top of each other when
70  *                necessary), then flatten them into one layer, combining advice
71  *                in the case of overlap, you get the chopped_list of advice.
72  *                Duplicate entries not allowed (since there is no overlap by
73  *                definition in this list).  List ordered by addr. Created once
74  *                per pid.
75  *
76  *                Example:
77  *                   merged_list:   |-----adv1----|---------adv3---------|
78  *                                       |--adv2--|--adv4--|-----adv5----|
79  *                                                  ||
80  *                                                  \/
81  *                   chopped_list:  |adv1|-adv1,2-|-adv3,4-|----adv3,5---|
82  *
83  *  maplist: list of memory mappings for a particular process. Used to create
84  *           generic advice entries for merged_list and for pmap like verbose
85  *           output. Created once per pid.
86  *
87  * Multiple lists are necessary because the actual advice applied given a set
88  * of generic and specific advice changes from process to process, so for each
89  * pid pmadvise is passed, it must create a new merged_list from which to apply
90  * advice (and a new chopped_list if verbose output is requested).
91  *
92  * Pseudo-code:
93  * I.	Input advice from command line
94  * II.	Create [raw advice list] of specific advice
95  * III.	Iterate through PIDs:
96  *	A.	Create [map list]
97  *	B.	Merge generic advice and [raw advice list] into [merged list]
98  *	C.	Apply advice from [merged list]; upon error:
99  *		i.	output madvise error message
100  *		ii.	remove element from [merged list]
101  *	D.	If verbose output:
102  *		i.	Create [chopped list] from [merged list]
103  *		ii.	Iterate through [map list]:
104  *			a.	output advice as given by [merged list]
105  *		iii.	Delete [chopped list]
106  *	E.	Delete [merged list]
107  *	F.	Delete [map list]
108  */
109 
110 #include <stdio.h>
111 #include <stdlib.h>
112 #include <unistd.h>
113 #include <ctype.h>
114 #include <fcntl.h>
115 #include <string.h>
116 #include <dirent.h>
117 #include <limits.h>
118 #include <link.h>
119 #include <libelf.h>
120 #include <locale.h>
121 #include <sys/types.h>
122 #include <sys/mman.h>
123 #include <sys/stat.h>
124 #include <sys/mkdev.h>
125 #include <assert.h>
126 #include <libproc.h>
127 #include <libgen.h>
128 #include <signal.h>
129 
130 #include "pmap_common.h"
131 
132 #ifndef	TEXT_DOMAIN			/* should be defined by cc -D */
133 #define	TEXT_DOMAIN	"SYS_TEST"	/* use this only if it wasn't */
134 #endif
135 
136 #define	KILOBYTE	1024
137 
138 /*
139  * Round up the value to the nearest kilobyte
140  */
141 #define	ROUNDUP_KB(x)	(((x) + (KILOBYTE - 1)) / KILOBYTE)
142 
143 #define	NO_ADVICE		0
144 
145 /*
146  * The following definitions are used as the third argument in insert_addr()
147  *   NODUPS = no duplicates are not allowed, thus if the addr being inserted
148  *   already exists in the list, return without inserting again.
149  *
150  *   YESDUPS = yes duplicates are allowed, thus always insert the addr
151  *   regardless of whether it already exists in the list or not.
152  */
153 #define	NODUPS	1
154 #define	YESDUPS	0
155 
156 /*
157  * Advice that can be passed to madvise fit into three groups that each
158  * contain 3 mutually exclusive options.  These groups are defined below:
159  *   Group 1: normal, random, sequential
160  *   Group 2: willneed, dontneed, free, purge
161  *   Group 3: default, accesslwp, accessmany
162  * Thus, advice that includes (at most) one from each group is valid.
163  *
164  * The following #define's are used as masks to determine which group(s) a
165  * particular advice fall under.
166  */
167 
168 #define	GRP1_ADV	(1 << MADV_NORMAL | 1 << MADV_RANDOM | \
169 			1 << MADV_SEQUENTIAL)
170 #define	GRP2_ADV	(1 << MADV_WILLNEED | 1 << MADV_DONTNEED | \
171 			1 << MADV_FREE | 1 << MADV_PURGE)
172 #define	GRP3_ADV	(1 << MADV_ACCESS_DEFAULT | 1 << MADV_ACCESS_LWP | \
173 			1 << MADV_ACCESS_MANY)
174 
175 static	int	create_maplist(void *, const prmap_t *, const char *);
176 static	int	pr_madvise(struct ps_prochandle *, caddr_t, size_t, int);
177 
178 static	char	*mflags(uint_t);
179 static	char	*advtostr(int);
180 
181 static	int	lflag = 0;
182 
183 static	int	addr_width, size_width;
184 static	char	*progname;
185 static	struct ps_prochandle *Pr;
186 
187 static	lwpstack_t *stacks;
188 static	uint_t	nstacks;
189 
190 static char	*suboptstr[] = {
191 	"private",
192 	"shared",
193 	"heap",
194 	"stack",
195 	NULL
196 };
197 
198 
199 int	generic_adv[] = {NO_ADVICE, NO_ADVICE, NO_ADVICE, NO_ADVICE};
200 int	at_map = 0;
201 
202 typedef struct saddr_struct {
203 	uintptr_t	addr;
204 	size_t		length;
205 	int		adv;
206 	struct saddr_struct	*next;
207 } saddr_t;
208 static int	apply_advice(saddr_t **);
209 static void	set_advice(int *, int);
210 static void	create_choplist(saddr_t **, saddr_t *);
211 
212 /*
213  * The segment address advice from the command line
214  */
215 saddr_t	*rawadv_list = NULL;
216 /*
217  * The rawadv_list + list entries for the generic advice (if any).
218  * This must be recreated for each PID as the memory maps might be different.
219  */
220 saddr_t *merged_list = NULL;
221 /*
222  * The merged_list cut up so as to remove all overlap
223  * e.g. if merged_list contained two entries:
224  *
225  * [0x38000:0x3e000) = adv1
226  * [0x3a000:0x3c000) = adv2
227  *
228  * the chopped list will contain three entries:
229  *
230  * [0x38000:0x3a000) = adv1
231  * [0x3a000:0x3c000) = adv1,adv2
232  * [0x3c000:0x3e000) = adv1
233  *
234  */
235 saddr_t *chopped_list = NULL;
236 
237 typedef struct mapnode_struct {
238 	prmap_t			*pmp;
239 	char			label[PATH_MAX];
240 	int			mtypes;
241 	struct mapnode_struct	*next;
242 } mapnode_t;
243 
244 mapnode_t *maplist_head = NULL;
245 mapnode_t *maplist_tail = NULL;
246 static void	print_advice(saddr_t *, mapnode_t *);
247 
248 int	opt_verbose;
249 
250 static char	*advicestr[] = {
251 	"normal",
252 	"random",
253 	"sequential",
254 	"willneed",
255 	"dontneed",
256 	"free",
257 	"access_default",
258 	"access_lwp",
259 	"access_many"
260 };
261 
262 /*
263  * How many signals caught from terminal
264  * We bail out as soon as possible when interrupt is set
265  */
266 static int	interrupt = 0;
267 
268 /*
269  * Interrupt handler
270  */
271 static void	intr(int);
272 
273 /*
274  * Iterative function passed to Plwp_iter to
275  * get alt and main stacks for given lwp.
276  */
277 static int
278 getstack(void *data, const lwpstatus_t *lsp)
279 {
280 	int *np = (int *)data;
281 
282 	if (Plwp_alt_stack(Pr, lsp->pr_lwpid, &stacks[*np].lwps_stack) == 0) {
283 		stacks[*np].lwps_stack.ss_flags |= SS_ONSTACK;
284 		stacks[*np].lwps_lwpid = lsp->pr_lwpid;
285 		(*np)++;
286 	}
287 
288 	if (Plwp_main_stack(Pr, lsp->pr_lwpid, &stacks[*np].lwps_stack) == 0) {
289 		stacks[*np].lwps_lwpid = lsp->pr_lwpid;
290 		(*np)++;
291 	}
292 
293 	return (0);
294 }
295 
296 /*
297  * Prints usage and exits
298  */
299 static void
300 usage()
301 {
302 	(void) fprintf(stderr,
303 	    gettext("usage:\t%s [-o option[,option]] [-Flv] pid ...\n"),
304 	    progname);
305 	(void) fprintf(stderr,
306 	    gettext("    (Give \"advice\" about a process's memory)\n"
307 	    "    -o option[,option]: options are\n"
308 	    "        private=<advice>\n"
309 	    "        shared=<advice>\n"
310 	    "        heap=<advice>\n"
311 	    "        stack=<advice>\n"
312 	    "        <segaddr>[:<length>]=<advice>\n"
313 	    "       valid <advice> is one of:\n"
314 	    "        normal, random, sequential, willneed, dontneed,\n"
315 	    "        free, access_lwp, access_many, access_default\n"
316 	    "    -v: verbose output\n"
317 	    "    -F: force grabbing of the target process(es)\n"
318 	    "    -l: show unresolved dynamic linker map names\n"
319 	    "    pid: process id list\n"));
320 	exit(2);
321 }
322 
323 /*
324  * Function to parse advice from options string
325  */
326 static int
327 get_advice(char *optarg)
328 {
329 	/*
330 	 * Determine which advice is given, we use shifted values as
331 	 * multiple pieces of advice may apply for a particular region.
332 	 * (See comment above regarding GRP[1,2,3]_ADV definitions for
333 	 * breakdown of advice groups).
334 	 */
335 	if (strcmp(optarg, "access_default") == 0)
336 		return (1 << MADV_ACCESS_DEFAULT);
337 	else if (strcmp(optarg, "access_many") == 0)
338 		return (1 << MADV_ACCESS_MANY);
339 	else if (strcmp(optarg, "access_lwp") == 0)
340 		return (1 << MADV_ACCESS_LWP);
341 	else if (strcmp(optarg, "sequential") == 0)
342 		return (1 << MADV_SEQUENTIAL);
343 	else if (strcmp(optarg, "willneed") == 0)
344 		return (1 << MADV_WILLNEED);
345 	else if (strcmp(optarg, "dontneed") == 0)
346 		return (1 << MADV_DONTNEED);
347 	else if (strcmp(optarg, "random") == 0)
348 		return (1 << MADV_RANDOM);
349 	else if (strcmp(optarg, "normal") == 0)
350 		return (1 << MADV_NORMAL);
351 	else if (strcmp(optarg, "free") == 0)
352 		return (1 << MADV_FREE);
353 	else if (strcmp(optarg, "purge") == 0)
354 		return (1 << MADV_PURGE);
355 	else {
356 		(void) fprintf(stderr, gettext("%s: invalid advice: %s\n"),
357 		    progname, optarg);
358 		usage();
359 		return (-1);
360 	}
361 }
362 
363 /*
364  * Function to convert character size indicators into actual size
365  * (i.e., 123M => sz = 123 * 1024 * 1024)
366  */
367 static size_t
368 atosz(char *optarg, char **endptr)
369 {
370 	size_t	sz = 0;
371 
372 	if (optarg == NULL || optarg[0] == '\0')
373 		return (0);
374 
375 	sz = strtoll(optarg, endptr, 0);
376 
377 	switch (**endptr) {
378 	case 'E':
379 	case 'e':
380 		sz *= KILOBYTE;
381 		/* FALLTHRU */
382 	case 'P':
383 	case 'p':
384 		sz *= KILOBYTE;
385 		/* FALLTHRU */
386 	case 'T':
387 	case 't':
388 		sz *= KILOBYTE;
389 		/* FALLTHRU */
390 	case 'G':
391 	case 'g':
392 		sz *= KILOBYTE;
393 		/* FALLTHRU */
394 	case 'M':
395 	case 'm':
396 		sz *= KILOBYTE;
397 		/* FALLTHRU */
398 	case 'K':
399 	case 'k':
400 		sz *= KILOBYTE;
401 		/* FALLTHRU */
402 	case 'B':
403 	case 'b':
404 		(*endptr)++;
405 		/* FALLTHRU */
406 	default:
407 		break;
408 	}
409 	return (sz);
410 }
411 
412 /*
413  * Inserts newaddr into list.  dups indicates whether we allow duplicate
414  * addr entries in the list (valid values are NODUPS and YESDUPS).
415  */
416 static void
417 insert_addr(saddr_t **list, saddr_t *newaddr, int dups)
418 {
419 	saddr_t *prev = *list;
420 	saddr_t *psaddr;
421 
422 	if (*list == NULL) {
423 		newaddr->next = *list;
424 		*list = newaddr;
425 		return;
426 	}
427 
428 	for (psaddr = (*list)->next; psaddr != NULL; psaddr = psaddr->next) {
429 		if ((dups == NODUPS) && (psaddr->addr == newaddr->addr)) {
430 			free(newaddr);
431 			return;
432 		}
433 
434 		/*
435 		 * primary level of comparison is by address; smaller addr 1st
436 		 * secondary level of comparison is by length; bigger length 1st
437 		 */
438 		if ((psaddr->addr > newaddr->addr) ||
439 		    (psaddr->addr == newaddr->addr &&
440 		    psaddr->length < newaddr->length))
441 			break;
442 
443 		prev = psaddr;
444 	}
445 
446 	prev->next = newaddr;
447 	newaddr->next = psaddr;
448 }
449 
450 /*
451  * Deletes given element from list
452  */
453 static void
454 delete_addr(saddr_t **list, saddr_t *delme)
455 {
456 	saddr_t	*prev = *list;
457 
458 	if (delme == *list) {
459 		*list = delme->next;
460 		free(delme);
461 		return;
462 	}
463 
464 	while (prev != NULL && prev->next != delme) {
465 		prev = prev->next;
466 	}
467 
468 	if (prev) {
469 		prev->next = delme->next;
470 		free(delme);
471 	}
472 }
473 
474 /*
475  * Delete entire list
476  */
477 static void
478 delete_list(saddr_t **list)
479 {
480 	saddr_t *psaddr = *list;
481 
482 	while (psaddr != NULL) {
483 		saddr_t *temp = psaddr;
484 
485 		psaddr = psaddr->next;
486 		free(temp);
487 	}
488 	*list = NULL;
489 }
490 
491 static saddr_t *
492 parse_suboptions(char *value)
493 {
494 	char	*endptr;
495 	saddr_t *psaddr = malloc(sizeof (saddr_t));
496 
497 	/*
498 	 * This must (better) be a segment addr
499 	 */
500 	psaddr->addr =
501 	    strtoull(value, &endptr, 16);
502 
503 	/*
504 	 * Check to make sure strtoul worked correctly (a properly formatted
505 	 * string will terminate in a ':' (if size is given) or an '=' (if size
506 	 * is not specified). Also check to make sure a 0 addr wasn't returned
507 	 * indicating strtoll was unable to convert).
508 	 */
509 	if ((psaddr->addr == 0) || (*endptr != ':' && *endptr != '=')) {
510 		free(psaddr);
511 		(void) fprintf(stderr,
512 		    gettext("%s: invalid option %s\n"),
513 		    progname, value);
514 		usage();
515 	} else {
516 		/* init other fields */
517 		psaddr->length = 0;
518 		psaddr->adv = NO_ADVICE;
519 		psaddr->next = NULL;
520 
521 		/* skip past address */
522 		value = endptr;
523 
524 		/* check for length */
525 		if (*value == ':') {
526 			/* skip the ":" */
527 			value++;
528 			psaddr->length = atosz(value, &endptr);
529 		}
530 
531 		if (*endptr != '=') {
532 			(void) fprintf(stderr,
533 			    gettext("%s: invalid option %s\n"),
534 			    progname, value);
535 			/*
536 			 * if improperly formatted, free mem, print usage, and
537 			 * exit Note: usage ends with a call to exit()
538 			 */
539 			free(psaddr);
540 			usage();
541 		}
542 		/* skip the "=" */
543 		value = endptr + 1;
544 		at_map |= (1 << AT_SEG);
545 		psaddr->adv =
546 		    get_advice(value);
547 	}
548 
549 	return (psaddr);
550 }
551 
552 /*
553  * Create linked list of mappings for current process
554  * In addition, add generic advice and raw advice
555  * entries to merged_list.
556  */
557 /* ARGSUSED */
558 static int
559 create_maplist(void *arg, const prmap_t *pmp, const char *object_name)
560 {
561 	const pstatus_t *Psp = Pstatus(Pr);
562 	mapnode_t *newmap = malloc(sizeof (mapnode_t));
563 	saddr_t	*newaddr;
564 	saddr_t	*psaddr;
565 	char	*lname = NULL;
566 	int	i;
567 
568 	if (interrupt)
569 		return (0);
570 
571 	newmap->pmp = malloc(sizeof (prmap_t));
572 	newmap->label[0] = '\0';
573 	newmap->mtypes = 0;
574 	newmap->next = NULL;
575 	(void) memcpy(newmap->pmp, pmp, sizeof (prmap_t));
576 
577 	/*
578 	 * If the mapping is not anon or not part of the heap, make a name
579 	 * for it.  We don't want to report the heap as a.out's data.
580 	 */
581 	if (!(pmp->pr_mflags & MA_ANON) ||
582 	    (pmp->pr_vaddr + pmp->pr_size <= Psp->pr_brkbase ||
583 	    pmp->pr_vaddr >= Psp->pr_brkbase + Psp->pr_brksize)) {
584 		lname = make_name(Pr, lflag, pmp->pr_vaddr, pmp->pr_mapname,
585 		    newmap->label, sizeof (newmap->label));
586 		if (pmp->pr_mflags & MA_SHARED)
587 			newmap->mtypes |= 1 << AT_SHARED;
588 		else
589 			newmap->mtypes |= 1 << AT_PRIVM;
590 	}
591 
592 	if (lname == NULL && (pmp->pr_mflags & MA_ANON)) {
593 		lname = anon_name(newmap->label, Psp, stacks, nstacks,
594 		    pmp->pr_vaddr, pmp->pr_size, pmp->pr_mflags, pmp->pr_shmid,
595 		    &newmap->mtypes);
596 	}
597 
598 	/*
599 	 * Add raw advice that applies to this mapping to the merged_list
600 	 */
601 	psaddr = rawadv_list;
602 	/*
603 	 * Advance to point in rawadv_list that applies to this mapping
604 	 */
605 	while (psaddr && psaddr->addr < pmp->pr_vaddr)
606 		psaddr = psaddr->next;
607 	/*
608 	 * Copy over to merged_list, check to see if size needs to be filled in
609 	 */
610 	while (psaddr && psaddr->addr < (pmp->pr_vaddr + pmp->pr_size)) {
611 		newaddr = malloc(sizeof (saddr_t));
612 		(void) memcpy(newaddr, psaddr, sizeof (saddr_t));
613 		insert_addr(&merged_list, newaddr, YESDUPS);
614 		/*
615 		 * For raw advice that is given without size, try to default
616 		 * size to size of mapping (only allowed if raw adv addr is
617 		 * equal to beginning of mapping). Don't change the entry
618 		 * in rawadv_list, only in the merged_list as the mappings
619 		 * (and thus the default sizes) will be different for
620 		 * different processes.
621 		 */
622 		if ((pmp->pr_vaddr == psaddr->addr) && (psaddr->length == 0))
623 			newaddr->length = pmp->pr_size;
624 		psaddr = psaddr->next;
625 	}
626 
627 	/*
628 	 * Put mapping into merged list with no advice, then
629 	 * check to see if any generic advice applies.
630 	 */
631 	newaddr = malloc(sizeof (saddr_t));
632 	newaddr->addr = pmp->pr_vaddr;
633 	newaddr->length = pmp->pr_size;
634 	newaddr->adv = NO_ADVICE;
635 	insert_addr(&merged_list, newaddr, YESDUPS);
636 
637 	newmap->mtypes &= at_map;
638 	for (i = AT_STACK; i >= AT_PRIVM; i--) {
639 		if (newmap->mtypes & (1 << i)) {
640 			assert(generic_adv[i] != NO_ADVICE);
641 			newaddr->adv = generic_adv[i];
642 			break;
643 		}
644 	}
645 
646 	/*
647 	 * Add to linked list of mappings
648 	 */
649 	if (maplist_tail == NULL) {
650 		maplist_head = maplist_tail = newmap;
651 	} else {
652 		maplist_tail->next = newmap;
653 		maplist_tail = newmap;
654 	}
655 
656 
657 	return (0);
658 }
659 
660 /*
661  * Traverse advice list and apply all applicable advice to each region
662  */
663 static int
664 apply_advice(saddr_t **advicelist)
665 {
666 	saddr_t	*psaddr = *advicelist;
667 	saddr_t	*next;
668 	int	i;
669 
670 
671 	while (!interrupt && psaddr != NULL) {
672 		/*
673 		 * Save next pointer since element may be removed before
674 		 * we get a chance to advance psaddr.
675 		 */
676 		next = psaddr->next;
677 
678 		/*
679 		 * Since mappings have been added to the merged list
680 		 * even if no generic advice was given for the map,
681 		 * check to make sure advice exists before bothering
682 		 * with the for loop.
683 		 */
684 		if (psaddr->adv != NO_ADVICE) {
685 			for (i = MADV_NORMAL; i <= MADV_PURGE; i++) {
686 				if ((psaddr->adv & (1 << i)) &&
687 				    (pr_madvise(Pr, (caddr_t)psaddr->addr,
688 				    psaddr->length, i) < 0)) {
689 					/*
690 					 * madvise(3C) call failed trying to
691 					 * apply advice output error and remove
692 					 * from advice list
693 					 */
694 					(void) fprintf(stderr,
695 					    gettext("Error applying "
696 					    "advice (%s) to memory range "
697 					    "[%lx, %lx):\n"),
698 					    advicestr[i], (ulong_t)psaddr->addr,
699 					    (ulong_t)psaddr->addr +
700 					    psaddr->length);
701 					perror("madvise");
702 					/*
703 					 * Clear this advice from the advice
704 					 * mask. If no more advice is given
705 					 * for this element, remove element
706 					 * from list.
707 					 */
708 					psaddr->adv &= ~(1 << i);
709 					if (psaddr->adv == 0) {
710 						delete_addr(advicelist, psaddr);
711 						break;
712 					}
713 				}
714 			}
715 		}
716 		psaddr = next;
717 	}
718 	return (0);
719 }
720 
721 /*
722  * Set advice but keep mutual exclusive property of advice groupings
723  */
724 static void
725 set_advice(int *combined_adv, int new_adv)
726 {
727 	/*
728 	 * Since advice falls in 3 groups of mutually exclusive options,
729 	 * clear previous value if new advice overwrites that group.
730 	 */
731 
732 	/*
733 	 * If this is the first advice to be applied, clear invalid value (-1)
734 	 */
735 	if (*combined_adv == -1)
736 		*combined_adv = 0;
737 
738 	if (new_adv & GRP1_ADV)
739 		*combined_adv &= ~GRP1_ADV;
740 	else if (new_adv & GRP2_ADV)
741 		*combined_adv &= ~GRP2_ADV;
742 	else
743 		*combined_adv &= ~GRP3_ADV;
744 
745 	*combined_adv |= new_adv;
746 }
747 
748 /*
749  * Create chopped list from merged list for use with verbose output
750  */
751 static void
752 create_choplist(saddr_t **choppedlist, saddr_t *mergedlist)
753 {
754 	saddr_t	*mlptr, *clptr;
755 
756 	for (mlptr = mergedlist; mlptr != NULL; mlptr = mlptr->next) {
757 		clptr = malloc(sizeof (saddr_t));
758 		clptr->addr = mlptr->addr;
759 		clptr->length = 0;
760 		/*
761 		 * Initialize the adv to -1 as an indicator for invalid
762 		 * elements in the chopped list (created from gaps between
763 		 * memory maps).
764 		 */
765 		clptr->adv = -1;
766 		clptr->next = NULL;
767 		insert_addr(choppedlist, clptr, NODUPS);
768 
769 		clptr = malloc(sizeof (saddr_t));
770 		clptr->addr = mlptr->addr + mlptr->length;
771 		clptr->length = 0;
772 		/*
773 		 * Again, initialize to -1 as an indicatorfor invalid elements
774 		 */
775 		clptr->adv = -1;
776 		clptr->next = NULL;
777 		insert_addr(choppedlist, clptr, NODUPS);
778 	}
779 
780 	for (clptr = *choppedlist; clptr != NULL; clptr = clptr->next) {
781 		if (clptr->next) {
782 			clptr->length = clptr->next->addr - clptr->addr;
783 		} else {
784 			/*
785 			 * must be last element, now that we've calculated
786 			 * all segment lengths, we can remove this node
787 			 */
788 			delete_addr(choppedlist, clptr);
789 			break;
790 		}
791 	}
792 
793 	for (mlptr = mergedlist; mlptr != NULL; mlptr = mlptr->next) {
794 		for (clptr = *choppedlist; clptr != NULL; clptr = clptr->next) {
795 			if (mlptr->addr <= clptr->addr &&
796 			    mlptr->addr + mlptr->length >=
797 			    clptr->addr + clptr->length)
798 				/*
799 				 * set_advice() will take care of conflicting
800 				 * advice by taking only the last advice
801 				 * applied for each of the 3 groups of advice.
802 				 */
803 				set_advice(&clptr->adv, mlptr->adv);
804 			if (mlptr->addr + mlptr->length <
805 			    clptr->addr)
806 				break;
807 		}
808 	}
809 }
810 
811 /*
812  * Print advice in pmap style for verbose output
813  */
814 static void
815 print_advice(saddr_t *advlist, mapnode_t *maplist)
816 {
817 	saddr_t		*psaddr = advlist;
818 	mapnode_t	*pmapnode;
819 	char		*advice;
820 
821 	pmapnode = maplist;
822 
823 	while (psaddr) {
824 		/*
825 		 * Using indicator flag from create_choppedlist, we know
826 		 * which entries in the chopped_list are gaps and should
827 		 * not be printed.
828 		 */
829 		if (psaddr->adv == -1) {
830 			psaddr = psaddr->next;
831 			continue;
832 		}
833 
834 		while (pmapnode && (pmapnode->pmp->pr_vaddr +
835 		    pmapnode->pmp->pr_size <= psaddr->addr))
836 			pmapnode = pmapnode->next;
837 
838 		advice = advtostr(psaddr->adv);
839 
840 		/*
841 		 * Print segment mapping and advice if there is any, or just a
842 		 * segment mapping.
843 		 */
844 		if (strlen(advice) > 0) {
845 			(void) printf("%.*lX %*uK %6s %s\t%s\n",
846 			    addr_width, (ulong_t)psaddr->addr, size_width - 1,
847 			    (int)ROUNDUP_KB(psaddr->length),
848 			    mflags(pmapnode->pmp->pr_mflags), pmapnode->label,
849 			    advice);
850 		} else {
851 			(void) printf("%.*lX %*uK %6s %s\n",
852 			    addr_width, (ulong_t)psaddr->addr, size_width - 1,
853 			    (int)ROUNDUP_KB(psaddr->length),
854 			    mflags(pmapnode->pmp->pr_mflags), pmapnode->label);
855 		}
856 		psaddr = psaddr->next;
857 
858 	}
859 }
860 
861 /*
862  * Call madvise(3c) in the context of the target process
863  */
864 static int
865 pr_madvise(struct ps_prochandle *Pr, caddr_t addr, size_t len, int advice)
866 {
867 	return (pr_memcntl(Pr, addr, len, MC_ADVISE,
868 	    (caddr_t)(uintptr_t)advice, 0, 0));
869 }
870 
871 static char *
872 mflags(uint_t arg)
873 {
874 	static char code_buf[80];
875 
876 	/*
877 	 * rwxsR
878 	 *
879 	 * r - segment is readable
880 	 * w - segment is writable
881 	 * x - segment is executable
882 	 * s - segment is shared
883 	 * R - segment is mapped MAP_NORESERVE
884 	 *
885 	 */
886 	(void) snprintf(code_buf, sizeof (code_buf), "%c%c%c%c%c ",
887 	    arg & MA_READ ? 'r' : '-',
888 	    arg & MA_WRITE ? 'w' : '-',
889 	    arg & MA_EXEC ? 'x' : '-',
890 	    arg & MA_SHARED ? 's' : '-',
891 	    arg & MA_NORESERVE ? 'R' : '-');
892 
893 	return (code_buf);
894 }
895 
896 /*
897  * Convert advice to a string containing a commented list of applicable advice
898  */
899 static char *
900 advtostr(int adv)
901 {
902 	static char buf[50];
903 	int i;
904 
905 	*buf = '\0';
906 
907 	if (adv != NO_ADVICE) {
908 		for (i = MADV_NORMAL; i <= MADV_PURGE; i++) {
909 			if (adv & (1 << i)) {
910 				/*
911 				 * check if it's the first advice entry
912 				 */
913 				if (*buf == '\0') {
914 					(void) snprintf(buf, sizeof (buf) - 1,
915 					    "<= %s", advicestr[i]);
916 				} else {
917 					(void) strlcat(buf, ",", sizeof (buf));
918 					(void) strlcat(buf, advicestr[i],
919 					    sizeof (buf));
920 				}
921 			}
922 		}
923 	}
924 
925 	return (buf);
926 }
927 
928 /*
929  * Handler for catching signals from terminal
930  */
931 /* ARGSUSED */
932 static void
933 intr(int sig)
934 {
935 	interrupt++;
936 }
937 
938 int
939 main(int argc, char **argv)
940 {
941 	int Fflag = 0;
942 	int rc = 0;
943 	int opt, subopt;
944 	int tmpadv;
945 	char	*options, *value;
946 	saddr_t	*psaddr;
947 	mapnode_t *pmapnode, *tempmapnode;
948 
949 	(void) setlocale(LC_ALL, "");
950 	(void) textdomain(TEXT_DOMAIN);
951 
952 	/*
953 	 * Get name of program for error messages
954 	 */
955 	progname = basename(argv[0]);
956 
957 	/*
958 	 * Not much to do when only name of program given
959 	 */
960 	if (argc == 1)
961 		usage();
962 
963 	/*
964 	 * Catch signals from terminal, so they can be handled asynchronously
965 	 * when we're ready instead of when we're not (;-)
966 	 */
967 	if (sigset(SIGHUP, SIG_IGN) == SIG_DFL)
968 		(void) sigset(SIGHUP, intr);
969 	if (sigset(SIGINT, SIG_IGN) == SIG_DFL)
970 		(void) sigset(SIGINT, intr);
971 	if (sigset(SIGQUIT, SIG_IGN) == SIG_DFL)
972 		(void) sigset(SIGQUIT, intr);
973 	(void) sigset(SIGPIPE, intr);
974 	(void) sigset(SIGTERM, intr);
975 
976 	/*
977 	 * Parse options, record generic advice if any and create
978 	 * rawadv_list from specific address advice.
979 	 */
980 
981 	while ((opt = getopt(argc, argv, "Flo:v")) != EOF) {
982 		switch (opt) {
983 		case 'o':
984 			options = optarg;
985 			while (*options != '\0') {
986 				subopt = getsubopt(&options, suboptstr,
987 				    &value);
988 				switch (subopt) {
989 				case AT_PRIVM:
990 				case AT_HEAP:
991 				case AT_SHARED:
992 				case AT_STACK:
993 					at_map |= (1 << subopt);
994 					tmpadv = get_advice(value);
995 					set_advice(&generic_adv[subopt],
996 					    tmpadv);
997 					break;
998 				default:
999 					at_map |= (1 << AT_SEG);
1000 					psaddr = parse_suboptions(value);
1001 					if (psaddr == NULL) {
1002 						usage();
1003 					} else {
1004 						insert_addr(&rawadv_list,
1005 						    psaddr, YESDUPS);
1006 					}
1007 					break;
1008 				}
1009 			}
1010 			break;
1011 		case 'v':
1012 			opt_verbose = 1;
1013 			break;
1014 		case 'F':		/* force grabbing (no O_EXCL) */
1015 			Fflag = PGRAB_FORCE;
1016 			break;
1017 		case 'l':		/* show unresolved link map names */
1018 			lflag = 1;
1019 			break;
1020 		default:
1021 			usage();
1022 			break;
1023 		}
1024 	}
1025 
1026 	argc -= optind;
1027 	argv += optind;
1028 
1029 	if (argc <= 0) {
1030 		usage();
1031 	}
1032 
1033 	(void) proc_initstdio();
1034 
1035 	/*
1036 	 * Iterate through all pid arguments, create new merged_list, maplist,
1037 	 * (and chopped_list if using verbose output) based on each process'
1038 	 * memory map.
1039 	 */
1040 
1041 	while (!interrupt && argc-- > 0) {
1042 		char *arg;
1043 		int gcode;
1044 		psinfo_t psinfo;
1045 
1046 		(void) proc_flushstdio();
1047 
1048 		if ((Pr = proc_arg_grab(arg = *argv++, PR_ARG_PIDS,
1049 		    PGRAB_RETAIN | Fflag, &gcode)) == NULL) {
1050 			(void) fprintf(stderr,
1051 			    gettext("%s: cannot examine %s: %s\n"),
1052 			    progname, arg, Pgrab_error(gcode));
1053 			rc++;
1054 			continue;
1055 		}
1056 
1057 
1058 		addr_width =
1059 		    (Pstatus(Pr)->pr_dmodel == PR_MODEL_LP64) ? 16 : 8;
1060 		size_width =
1061 		    (Pstatus(Pr)->pr_dmodel == PR_MODEL_LP64) ? 11 : 8;
1062 		(void) memcpy(&psinfo, Ppsinfo(Pr), sizeof (psinfo_t));
1063 
1064 		if (opt_verbose) {
1065 			proc_unctrl_psinfo(&psinfo);
1066 			(void) printf("%d:\t%.70s\n",
1067 			    (int)psinfo.pr_pid, psinfo.pr_psargs);
1068 		}
1069 
1070 		/*
1071 		 * Get mappings for a process unless it is a system process.
1072 		 */
1073 		if (!(Pstatus(Pr)->pr_flags & PR_ISSYS)) {
1074 			nstacks = psinfo.pr_nlwp * 2;
1075 			stacks = calloc(nstacks, sizeof (stacks[0]));
1076 			if (stacks != NULL) {
1077 				int n = 0;
1078 				(void) Plwp_iter(Pr, getstack, &n);
1079 				qsort(stacks, nstacks, sizeof (stacks[0]),
1080 				    cmpstacks);
1081 			}
1082 
1083 			if (Pgetauxval(Pr, AT_BASE) != -1L &&
1084 			    Prd_agent(Pr) == NULL) {
1085 				(void) fprintf(stderr,
1086 				    gettext("%s: warning: "
1087 				    "librtld_db failed to initialize; "
1088 				    "shared library information will not "
1089 				    "be available\n"),
1090 				    progname);
1091 			}
1092 
1093 			/*
1094 			 * Create linked list of mappings for current process
1095 			 * In addition, add generic advice and raw advice
1096 			 * entries to merged_list.
1097 			 * e.g. if rawadv_list contains:
1098 			 *   [0x38000,0x3a000) = adv1
1099 			 *   [0x3a000,0x3c000) = adv2
1100 			 * and there is generic advice:
1101 			 *   heap = adv3
1102 			 * where heap corresponds to 0x38000, then merged_list
1103 			 * will contain:
1104 			 *   ... (include all other mappings from process)
1105 			 *   [0x38000,0x3c000) = adv3
1106 			 *   [0x38000,0x3a000) = adv1
1107 			 *   [0x3a000,0x3c000) = adv2
1108 			 *   ... (include all other mappings from process)
1109 			 */
1110 			assert(merged_list == NULL);
1111 			maplist_head = maplist_tail = NULL;
1112 			rc += Pmapping_iter(Pr, (proc_map_f *)create_maplist,
1113 			    NULL);
1114 
1115 			/*
1116 			 * Apply advice by iterating through merged list
1117 			 */
1118 			(void) apply_advice(&merged_list);
1119 
1120 			if (opt_verbose) {
1121 				assert(chopped_list == NULL);
1122 				/*
1123 				 * Create chopped_list from merged_list
1124 				 */
1125 				create_choplist(&chopped_list, merged_list);
1126 
1127 				/*
1128 				 * Iterate through maplist and output as
1129 				 * given by chopped_list
1130 				 */
1131 				print_advice(chopped_list, maplist_head);
1132 				delete_list(&chopped_list);
1133 			}
1134 
1135 			delete_list(&merged_list);
1136 
1137 			/*
1138 			 * Clear maplist
1139 			 */
1140 			pmapnode = maplist_head;
1141 			while (pmapnode) {
1142 				tempmapnode = pmapnode;
1143 				pmapnode = pmapnode->next;
1144 				free(tempmapnode);
1145 			}
1146 
1147 			if (stacks != NULL) {
1148 				free(stacks);
1149 				stacks = NULL;
1150 			}
1151 		}
1152 
1153 		Prelease(Pr, 0);
1154 	}
1155 
1156 	(void) proc_finistdio();
1157 
1158 	return (rc);
1159 }
1160