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