root/usr/src/cmd/ptools/pmadvise/pmadvise.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright (c) 2015, Joyent, Inc. All rights reserved.
 * Copyright 2025 Oxide Computer Company
 */

/*
 * pmadvise
 *
 * ptool wrapper for madvise(3C) to apply memory advice to running processes
 *
 * usage:       pmadvise -o option[,option] [-v] [-F] pid ...
 *  (Give "advice" about a process's memory)
 *  -o option[,option]: options are
 *      private=<advice>
 *      shared=<advice>
 *      heap=<advice>
 *      stack=<advice>
 *      <segaddr>[:<length>]=<advice>
 *     valid <advice> is one of:
 *      normal, random, sequential, willneed, dontneed,
 *      free, access_lwp, access_many, access_default
 *  -v: verbose output
 *  -F: force grabbing of the target process(es)
 *  -l: show unresolved dynamic linker map names
 *  pid: process id list
 *
 *
 * Advice passed to this tool are organized into various lists described here:
 *  rawadv_list: includes all specific advice from command line (specific
 *               advice being those given to a particular address range rather
 *               than a type like "heap" or "stack".  In contrast, these
 *               types are referred to as generic advice). Duplicates allowed.
 *               List ordered by addr, then by size (largest size first).
 *               Created once per run.
 *  merged_list: includes all specific advice from the rawadv_list as well as
 *               all generic advice.  This must be recreated for each process
 *               as the generic advice will apply to different regions for
 *               different processes. Duplicates allowed. List ordered by addr,
 *               then by size (largest size first). Created once per pid.
 *  chopped_list: used for verbose output only. This list parses the merged
 *                list such that it eliminates any overlap and combines the
 *                advice. Easiest to think of this visually: if you take all
 *                the advice in the merged list and lay them down on a memory
 *                range of the entire process (laying on top of each other when
 *                necessary), then flatten them into one layer, combining advice
 *                in the case of overlap, you get the chopped_list of advice.
 *                Duplicate entries not allowed (since there is no overlap by
 *                definition in this list).  List ordered by addr. Created once
 *                per pid.
 *
 *                Example:
 *                   merged_list:   |-----adv1----|---------adv3---------|
 *                                       |--adv2--|--adv4--|-----adv5----|
 *                                                  ||
 *                                                  \/
 *                   chopped_list:  |adv1|-adv1,2-|-adv3,4-|----adv3,5---|
 *
 *  maplist: list of memory mappings for a particular process. Used to create
 *           generic advice entries for merged_list and for pmap like verbose
 *           output. Created once per pid.
 *
 * Multiple lists are necessary because the actual advice applied given a set
 * of generic and specific advice changes from process to process, so for each
 * pid pmadvise is passed, it must create a new merged_list from which to apply
 * advice (and a new chopped_list if verbose output is requested).
 *
 * Pseudo-code:
 * I.   Input advice from command line
 * II.  Create [raw advice list] of specific advice
 * III. Iterate through PIDs:
 *      A.      Create [map list]
 *      B.      Merge generic advice and [raw advice list] into [merged list]
 *      C.      Apply advice from [merged list]; upon error:
 *              i.      output madvise error message
 *              ii.     remove element from [merged list]
 *      D.      If verbose output:
 *              i.      Create [chopped list] from [merged list]
 *              ii.     Iterate through [map list]:
 *                      a.      output advice as given by [merged list]
 *              iii.    Delete [chopped list]
 *      E.      Delete [merged list]
 *      F.      Delete [map list]
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>
#include <string.h>
#include <dirent.h>
#include <limits.h>
#include <link.h>
#include <libelf.h>
#include <locale.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/mkdev.h>
#include <assert.h>
#include <libproc.h>
#include <libgen.h>
#include <signal.h>

#include "pmap_common.h"

#ifndef TEXT_DOMAIN                     /* should be defined by cc -D */
#define TEXT_DOMAIN     "SYS_TEST"      /* use this only if it wasn't */
#endif

#define KILOBYTE        1024

/*
 * Round up the value to the nearest kilobyte
 */
#define ROUNDUP_KB(x)   (((x) + (KILOBYTE - 1)) / KILOBYTE)

#define INVALID_ADDRESS         (uintptr_t)(-1)

#define NO_ADVICE               0

/*
 * The following definitions are used as the third argument in insert_addr()
 *   NODUPS = no duplicates are not allowed, thus if the addr being inserted
 *   already exists in the list, return without inserting again.
 *
 *   YESDUPS = yes duplicates are allowed, thus always insert the addr
 *   regardless of whether it already exists in the list or not.
 */
#define NODUPS  1
#define YESDUPS 0

/*
 * Advice that can be passed to madvise fit into three groups that each
 * contain 3 mutually exclusive options.  These groups are defined below:
 *   Group 1: normal, random, sequential
 *   Group 2: willneed, dontneed, free, purge
 *   Group 3: default, accesslwp, accessmany
 * Thus, advice that includes (at most) one from each group is valid.
 *
 * The following #define's are used as masks to determine which group(s) a
 * particular advice fall under.
 */

#define GRP1_ADV        (1 << MADV_NORMAL | 1 << MADV_RANDOM | \
                        1 << MADV_SEQUENTIAL)
#define GRP2_ADV        (1 << MADV_WILLNEED | 1 << MADV_DONTNEED | \
                        1 << MADV_FREE | 1 << MADV_PURGE)
#define GRP3_ADV        (1 << MADV_ACCESS_DEFAULT | 1 << MADV_ACCESS_LWP | \
                        1 << MADV_ACCESS_MANY)

static  int     create_maplist(void *, const prmap_t *, const char *);
static  int     pr_madvise(struct ps_prochandle *, caddr_t, size_t, int);

static  char    *mflags(uint_t);
static  char    *advtostr(int);

static  int     lflag = 0;

static  int     addr_width, size_width;
static  char    *progname;
static  struct ps_prochandle *Pr;

static  lwpstack_t *stacks;
static  uint_t  nstacks;

static uintptr_t comm_page = INVALID_ADDRESS;

static char     *suboptstr[] = {
        "private",
        "shared",
        "heap",
        "stack",
        NULL
};


int     generic_adv[] = {NO_ADVICE, NO_ADVICE, NO_ADVICE, NO_ADVICE};
int     at_map = 0;

typedef struct saddr_struct {
        uintptr_t       addr;
        size_t          length;
        int             adv;
        struct saddr_struct     *next;
} saddr_t;
static int      apply_advice(saddr_t **);
static void     set_advice(int *, int);
static void     create_choplist(saddr_t **, saddr_t *);

/*
 * The segment address advice from the command line
 */
saddr_t *rawadv_list = NULL;
/*
 * The rawadv_list + list entries for the generic advice (if any).
 * This must be recreated for each PID as the memory maps might be different.
 */
saddr_t *merged_list = NULL;
/*
 * The merged_list cut up so as to remove all overlap
 * e.g. if merged_list contained two entries:
 *
 * [0x38000:0x3e000) = adv1
 * [0x3a000:0x3c000) = adv2
 *
 * the chopped list will contain three entries:
 *
 * [0x38000:0x3a000) = adv1
 * [0x3a000:0x3c000) = adv1,adv2
 * [0x3c000:0x3e000) = adv1
 *
 */
saddr_t *chopped_list = NULL;

typedef struct mapnode_struct {
        prmap_t                 *pmp;
        char                    label[PATH_MAX];
        int                     mtypes;
        struct mapnode_struct   *next;
} mapnode_t;

mapnode_t *maplist_head = NULL;
mapnode_t *maplist_tail = NULL;
static void     print_advice(saddr_t *, mapnode_t *);

int     opt_verbose;

static char     *advicestr[] = {
        "normal",
        "random",
        "sequential",
        "willneed",
        "dontneed",
        "free",
        "access_default",
        "access_lwp",
        "access_many"
};

/*
 * How many signals caught from terminal
 * We bail out as soon as possible when interrupt is set
 */
static int      interrupt = 0;

/*
 * Interrupt handler
 */
static void     intr(int);

/*
 * Iterative function passed to Plwp_iter to
 * get alt and main stacks for given lwp.
 */
static int
getstack(void *data, const lwpstatus_t *lsp)
{
        int *np = (int *)data;

        if (Plwp_alt_stack(Pr, lsp->pr_lwpid, &stacks[*np].lwps_stack) == 0) {
                stacks[*np].lwps_stack.ss_flags |= SS_ONSTACK;
                stacks[*np].lwps_lwpid = lsp->pr_lwpid;
                (*np)++;
        }

        if (Plwp_main_stack(Pr, lsp->pr_lwpid, &stacks[*np].lwps_stack) == 0) {
                stacks[*np].lwps_lwpid = lsp->pr_lwpid;
                (*np)++;
        }

        return (0);
}

/*
 * Prints usage and exits
 */
static void
usage()
{
        (void) fprintf(stderr,
            gettext("usage:\t%s [-o option[,option]] [-Flv] pid ...\n"),
            progname);
        (void) fprintf(stderr,
            gettext("    (Give \"advice\" about a process's memory)\n"
            "    -o option[,option]: options are\n"
            "        private=<advice>\n"
            "        shared=<advice>\n"
            "        heap=<advice>\n"
            "        stack=<advice>\n"
            "        <segaddr>[:<length>]=<advice>\n"
            "       valid <advice> is one of:\n"
            "        normal, random, sequential, willneed, dontneed,\n"
            "        free, access_lwp, access_many, access_default\n"
            "    -v: verbose output\n"
            "    -F: force grabbing of the target process(es)\n"
            "    -l: show unresolved dynamic linker map names\n"
            "    pid: process id list\n"));
        exit(2);
}

/*
 * Function to parse advice from options string
 */
static int
get_advice(char *optarg)
{
        /*
         * Determine which advice is given, we use shifted values as
         * multiple pieces of advice may apply for a particular region.
         * (See comment above regarding GRP[1,2,3]_ADV definitions for
         * breakdown of advice groups).
         */
        if (strcmp(optarg, "access_default") == 0)
                return (1 << MADV_ACCESS_DEFAULT);
        else if (strcmp(optarg, "access_many") == 0)
                return (1 << MADV_ACCESS_MANY);
        else if (strcmp(optarg, "access_lwp") == 0)
                return (1 << MADV_ACCESS_LWP);
        else if (strcmp(optarg, "sequential") == 0)
                return (1 << MADV_SEQUENTIAL);
        else if (strcmp(optarg, "willneed") == 0)
                return (1 << MADV_WILLNEED);
        else if (strcmp(optarg, "dontneed") == 0)
                return (1 << MADV_DONTNEED);
        else if (strcmp(optarg, "random") == 0)
                return (1 << MADV_RANDOM);
        else if (strcmp(optarg, "normal") == 0)
                return (1 << MADV_NORMAL);
        else if (strcmp(optarg, "free") == 0)
                return (1 << MADV_FREE);
        else if (strcmp(optarg, "purge") == 0)
                return (1 << MADV_PURGE);
        else {
                (void) fprintf(stderr, gettext("%s: invalid advice: %s\n"),
                    progname, optarg);
                usage();
                return (-1);
        }
}

/*
 * Function to convert character size indicators into actual size
 * (i.e., 123M => sz = 123 * 1024 * 1024)
 */
static size_t
atosz(char *optarg, char **endptr)
{
        size_t  sz = 0;

        if (optarg == NULL || optarg[0] == '\0')
                return (0);

        sz = strtoll(optarg, endptr, 0);

        switch (**endptr) {
        case 'E':
        case 'e':
                sz *= KILOBYTE;
                /* FALLTHRU */
        case 'P':
        case 'p':
                sz *= KILOBYTE;
                /* FALLTHRU */
        case 'T':
        case 't':
                sz *= KILOBYTE;
                /* FALLTHRU */
        case 'G':
        case 'g':
                sz *= KILOBYTE;
                /* FALLTHRU */
        case 'M':
        case 'm':
                sz *= KILOBYTE;
                /* FALLTHRU */
        case 'K':
        case 'k':
                sz *= KILOBYTE;
                /* FALLTHRU */
        case 'B':
        case 'b':
                (*endptr)++;
                /* FALLTHRU */
        default:
                break;
        }
        return (sz);
}

/*
 * Inserts newaddr into list.  dups indicates whether we allow duplicate
 * addr entries in the list (valid values are NODUPS and YESDUPS).
 */
static void
insert_addr(saddr_t **list, saddr_t *newaddr, int dups)
{
        saddr_t *prev = *list;
        saddr_t *psaddr;

        if (*list == NULL) {
                newaddr->next = *list;
                *list = newaddr;
                return;
        }

        for (psaddr = (*list)->next; psaddr != NULL; psaddr = psaddr->next) {
                if ((dups == NODUPS) && (psaddr->addr == newaddr->addr)) {
                        free(newaddr);
                        return;
                }

                /*
                 * primary level of comparison is by address; smaller addr 1st
                 * secondary level of comparison is by length; bigger length 1st
                 */
                if ((psaddr->addr > newaddr->addr) ||
                    (psaddr->addr == newaddr->addr &&
                    psaddr->length < newaddr->length))
                        break;

                prev = psaddr;
        }

        prev->next = newaddr;
        newaddr->next = psaddr;
}

/*
 * Deletes given element from list
 */
static void
delete_addr(saddr_t **list, saddr_t *delme)
{
        saddr_t *prev = *list;

        if (delme == *list) {
                *list = delme->next;
                free(delme);
                return;
        }

        while (prev != NULL && prev->next != delme) {
                prev = prev->next;
        }

        if (prev) {
                prev->next = delme->next;
                free(delme);
        }
}

/*
 * Delete entire list
 */
static void
delete_list(saddr_t **list)
{
        saddr_t *psaddr = *list;

        while (psaddr != NULL) {
                saddr_t *temp = psaddr;

                psaddr = psaddr->next;
                free(temp);
        }
        *list = NULL;
}

static saddr_t *
parse_suboptions(char *value)
{
        char    *endptr;
        saddr_t *psaddr = malloc(sizeof (saddr_t));

        /*
         * This must (better) be a segment addr
         */
        psaddr->addr =
            strtoull(value, &endptr, 16);

        /*
         * Check to make sure strtoul worked correctly (a properly formatted
         * string will terminate in a ':' (if size is given) or an '=' (if size
         * is not specified). Also check to make sure a 0 addr wasn't returned
         * indicating strtoll was unable to convert).
         */
        if ((psaddr->addr == 0) || (*endptr != ':' && *endptr != '=')) {
                free(psaddr);
                (void) fprintf(stderr,
                    gettext("%s: invalid option %s\n"),
                    progname, value);
                usage();
        } else {
                /* init other fields */
                psaddr->length = 0;
                psaddr->adv = NO_ADVICE;
                psaddr->next = NULL;

                /* skip past address */
                value = endptr;

                /* check for length */
                if (*value == ':') {
                        /* skip the ":" */
                        value++;
                        psaddr->length = atosz(value, &endptr);
                }

                if (*endptr != '=') {
                        (void) fprintf(stderr,
                            gettext("%s: invalid option %s\n"),
                            progname, value);
                        /*
                         * if improperly formatted, free mem, print usage, and
                         * exit Note: usage ends with a call to exit()
                         */
                        free(psaddr);
                        usage();
                }
                /* skip the "=" */
                value = endptr + 1;
                at_map |= (1 << AT_SEG);
                psaddr->adv =
                    get_advice(value);
        }

        return (psaddr);
}

/*
 * Create linked list of mappings for current process
 * In addition, add generic advice and raw advice
 * entries to merged_list.
 */
/* ARGSUSED */
static int
create_maplist(void *arg, const prmap_t *pmp, const char *object_name)
{
        const pstatus_t *Psp = Pstatus(Pr);
        mapnode_t *newmap = malloc(sizeof (mapnode_t));
        saddr_t *newaddr;
        saddr_t *psaddr;
        char    *lname = NULL;
        int     i;

        if (interrupt)
                return (0);

        newmap->pmp = malloc(sizeof (prmap_t));
        newmap->label[0] = '\0';
        newmap->mtypes = 0;
        newmap->next = NULL;
        (void) memcpy(newmap->pmp, pmp, sizeof (prmap_t));

        /*
         * If the mapping is not anon or not part of the heap, make a name
         * for it.  We don't want to report the heap as a.out's data.
         */
        if (!(pmp->pr_mflags & MA_ANON) ||
            (pmp->pr_vaddr + pmp->pr_size <= Psp->pr_brkbase ||
            pmp->pr_vaddr >= Psp->pr_brkbase + Psp->pr_brksize)) {
                lname = make_name(Pr, lflag, pmp->pr_vaddr, pmp->pr_mapname,
                    newmap->label, sizeof (newmap->label));
                if (pmp->pr_mflags & MA_SHARED)
                        newmap->mtypes |= 1 << AT_SHARED;
                else
                        newmap->mtypes |= 1 << AT_PRIVM;
        }

        if (lname == NULL && (pmp->pr_mflags & MA_ANON)) {
                lname = anon_name(newmap->label, Psp, stacks, nstacks,
                    pmp->pr_vaddr, pmp->pr_size, pmp->pr_mflags, pmp->pr_shmid,
                    &newmap->mtypes);
        }

        if (lname == NULL && comm_page != INVALID_ADDRESS &&
            pmp->pr_vaddr == comm_page) {
                (void) strlcpy(newmap->label, "  [ comm ]",
                    sizeof (newmap->label));
                lname = newmap->label;
        }

        /*
         * Add raw advice that applies to this mapping to the merged_list
         */
        psaddr = rawadv_list;
        /*
         * Advance to point in rawadv_list that applies to this mapping
         */
        while (psaddr && psaddr->addr < pmp->pr_vaddr)
                psaddr = psaddr->next;
        /*
         * Copy over to merged_list, check to see if size needs to be filled in
         */
        while (psaddr && psaddr->addr < (pmp->pr_vaddr + pmp->pr_size)) {
                newaddr = malloc(sizeof (saddr_t));
                (void) memcpy(newaddr, psaddr, sizeof (saddr_t));
                insert_addr(&merged_list, newaddr, YESDUPS);
                /*
                 * For raw advice that is given without size, try to default
                 * size to size of mapping (only allowed if raw adv addr is
                 * equal to beginning of mapping). Don't change the entry
                 * in rawadv_list, only in the merged_list as the mappings
                 * (and thus the default sizes) will be different for
                 * different processes.
                 */
                if ((pmp->pr_vaddr == psaddr->addr) && (psaddr->length == 0))
                        newaddr->length = pmp->pr_size;
                psaddr = psaddr->next;
        }

        /*
         * Put mapping into merged list with no advice, then
         * check to see if any generic advice applies.
         */
        newaddr = malloc(sizeof (saddr_t));
        newaddr->addr = pmp->pr_vaddr;
        newaddr->length = pmp->pr_size;
        newaddr->adv = NO_ADVICE;
        insert_addr(&merged_list, newaddr, YESDUPS);

        newmap->mtypes &= at_map;
        for (i = AT_STACK; i >= AT_PRIVM; i--) {
                if (newmap->mtypes & (1 << i)) {
                        assert(generic_adv[i] != NO_ADVICE);
                        newaddr->adv = generic_adv[i];
                        break;
                }
        }

        /*
         * Add to linked list of mappings
         */
        if (maplist_tail == NULL) {
                maplist_head = maplist_tail = newmap;
        } else {
                maplist_tail->next = newmap;
                maplist_tail = newmap;
        }


        return (0);
}

/*
 * Traverse advice list and apply all applicable advice to each region
 */
static int
apply_advice(saddr_t **advicelist)
{
        saddr_t *psaddr = *advicelist;
        saddr_t *next;
        int     i;


        while (!interrupt && psaddr != NULL) {
                /*
                 * Save next pointer since element may be removed before
                 * we get a chance to advance psaddr.
                 */
                next = psaddr->next;

                /*
                 * Since mappings have been added to the merged list
                 * even if no generic advice was given for the map,
                 * check to make sure advice exists before bothering
                 * with the for loop.
                 */
                if (psaddr->adv != NO_ADVICE) {
                        for (i = MADV_NORMAL; i <= MADV_PURGE; i++) {
                                if ((psaddr->adv & (1 << i)) &&
                                    (pr_madvise(Pr, (caddr_t)psaddr->addr,
                                    psaddr->length, i) < 0)) {
                                        /*
                                         * madvise(3C) call failed trying to
                                         * apply advice output error and remove
                                         * from advice list
                                         */
                                        (void) fprintf(stderr,
                                            gettext("Error applying "
                                            "advice (%s) to memory range "
                                            "[%lx, %lx):\n"),
                                            advicestr[i], (ulong_t)psaddr->addr,
                                            (ulong_t)psaddr->addr +
                                            psaddr->length);
                                        perror("madvise");
                                        /*
                                         * Clear this advice from the advice
                                         * mask. If no more advice is given
                                         * for this element, remove element
                                         * from list.
                                         */
                                        psaddr->adv &= ~(1 << i);
                                        if (psaddr->adv == 0) {
                                                delete_addr(advicelist, psaddr);
                                                break;
                                        }
                                }
                        }
                }
                psaddr = next;
        }
        return (0);
}

/*
 * Set advice but keep mutual exclusive property of advice groupings
 */
static void
set_advice(int *combined_adv, int new_adv)
{
        /*
         * Since advice falls in 3 groups of mutually exclusive options,
         * clear previous value if new advice overwrites that group.
         */

        /*
         * If this is the first advice to be applied, clear invalid value (-1)
         */
        if (*combined_adv == -1)
                *combined_adv = 0;

        if (new_adv & GRP1_ADV)
                *combined_adv &= ~GRP1_ADV;
        else if (new_adv & GRP2_ADV)
                *combined_adv &= ~GRP2_ADV;
        else
                *combined_adv &= ~GRP3_ADV;

        *combined_adv |= new_adv;
}

/*
 * Create chopped list from merged list for use with verbose output
 */
static void
create_choplist(saddr_t **choppedlist, saddr_t *mergedlist)
{
        saddr_t *mlptr, *clptr;

        for (mlptr = mergedlist; mlptr != NULL; mlptr = mlptr->next) {
                clptr = malloc(sizeof (saddr_t));
                clptr->addr = mlptr->addr;
                clptr->length = 0;
                /*
                 * Initialize the adv to -1 as an indicator for invalid
                 * elements in the chopped list (created from gaps between
                 * memory maps).
                 */
                clptr->adv = -1;
                clptr->next = NULL;
                insert_addr(choppedlist, clptr, NODUPS);

                clptr = malloc(sizeof (saddr_t));
                clptr->addr = mlptr->addr + mlptr->length;
                clptr->length = 0;
                /*
                 * Again, initialize to -1 as an indicatorfor invalid elements
                 */
                clptr->adv = -1;
                clptr->next = NULL;
                insert_addr(choppedlist, clptr, NODUPS);
        }

        for (clptr = *choppedlist; clptr != NULL; clptr = clptr->next) {
                if (clptr->next) {
                        clptr->length = clptr->next->addr - clptr->addr;
                } else {
                        /*
                         * must be last element, now that we've calculated
                         * all segment lengths, we can remove this node
                         */
                        delete_addr(choppedlist, clptr);
                        break;
                }
        }

        for (mlptr = mergedlist; mlptr != NULL; mlptr = mlptr->next) {
                for (clptr = *choppedlist; clptr != NULL; clptr = clptr->next) {
                        if (mlptr->addr <= clptr->addr &&
                            mlptr->addr + mlptr->length >=
                            clptr->addr + clptr->length)
                                /*
                                 * set_advice() will take care of conflicting
                                 * advice by taking only the last advice
                                 * applied for each of the 3 groups of advice.
                                 */
                                set_advice(&clptr->adv, mlptr->adv);
                        if (mlptr->addr + mlptr->length <
                            clptr->addr)
                                break;
                }
        }
}

/*
 * Print advice in pmap style for verbose output
 */
static void
print_advice(saddr_t *advlist, mapnode_t *maplist)
{
        saddr_t         *psaddr = advlist;
        mapnode_t       *pmapnode;
        char            *advice;

        pmapnode = maplist;

        while (psaddr) {
                /*
                 * Using indicator flag from create_choppedlist, we know
                 * which entries in the chopped_list are gaps and should
                 * not be printed.
                 */
                if (psaddr->adv == -1) {
                        psaddr = psaddr->next;
                        continue;
                }

                while (pmapnode && (pmapnode->pmp->pr_vaddr +
                    pmapnode->pmp->pr_size <= psaddr->addr))
                        pmapnode = pmapnode->next;

                advice = advtostr(psaddr->adv);

                /*
                 * Print segment mapping and advice if there is any, or just a
                 * segment mapping.
                 */
                if (strlen(advice) > 0) {
                        (void) printf("%.*lX %*uK %6s %s\t%s\n",
                            addr_width, (ulong_t)psaddr->addr, size_width - 1,
                            (int)ROUNDUP_KB(psaddr->length),
                            mflags(pmapnode->pmp->pr_mflags), pmapnode->label,
                            advice);
                } else {
                        (void) printf("%.*lX %*uK %6s %s\n",
                            addr_width, (ulong_t)psaddr->addr, size_width - 1,
                            (int)ROUNDUP_KB(psaddr->length),
                            mflags(pmapnode->pmp->pr_mflags), pmapnode->label);
                }
                psaddr = psaddr->next;

        }
}

/*
 * Call madvise(3c) in the context of the target process
 */
static int
pr_madvise(struct ps_prochandle *Pr, caddr_t addr, size_t len, int advice)
{
        return (pr_memcntl(Pr, addr, len, MC_ADVISE,
            (caddr_t)(uintptr_t)advice, 0, 0));
}

static char *
mflags(uint_t arg)
{
        static char code_buf[80];

        /*
         * rwxsR
         *
         * r - segment is readable
         * w - segment is writable
         * x - segment is executable
         * s - segment is shared
         * R - segment is mapped MAP_NORESERVE
         *
         */
        (void) snprintf(code_buf, sizeof (code_buf), "%c%c%c%c%c ",
            arg & MA_READ ? 'r' : '-',
            arg & MA_WRITE ? 'w' : '-',
            arg & MA_EXEC ? 'x' : '-',
            arg & MA_SHARED ? 's' : '-',
            arg & MA_NORESERVE ? 'R' : '-');

        return (code_buf);
}

/*
 * Convert advice to a string containing a commented list of applicable advice
 */
static char *
advtostr(int adv)
{
        static char buf[50];
        int i;

        *buf = '\0';

        if (adv != NO_ADVICE) {
                for (i = MADV_NORMAL; i <= MADV_PURGE; i++) {
                        if (adv & (1 << i)) {
                                /*
                                 * check if it's the first advice entry
                                 */
                                if (*buf == '\0') {
                                        (void) snprintf(buf, sizeof (buf) - 1,
                                            "<= %s", advicestr[i]);
                                } else {
                                        (void) strlcat(buf, ",", sizeof (buf));
                                        (void) strlcat(buf, advicestr[i],
                                            sizeof (buf));
                                }
                        }
                }
        }

        return (buf);
}

/*
 * Handler for catching signals from terminal
 */
/* ARGSUSED */
static void
intr(int sig)
{
        interrupt++;
}

int
main(int argc, char **argv)
{
        int Fflag = 0;
        int rc = 0;
        int opt, subopt;
        int tmpadv;
        char    *options, *value;
        saddr_t *psaddr;
        mapnode_t *pmapnode, *tempmapnode;

        (void) setlocale(LC_ALL, "");
        (void) textdomain(TEXT_DOMAIN);

        /*
         * Get name of program for error messages
         */
        progname = basename(argv[0]);

        /*
         * Not much to do when only name of program given
         */
        if (argc == 1)
                usage();

        /*
         * Catch signals from terminal, so they can be handled asynchronously
         * when we're ready instead of when we're not (;-)
         */
        if (sigset(SIGHUP, SIG_IGN) == SIG_DFL)
                (void) sigset(SIGHUP, intr);
        if (sigset(SIGINT, SIG_IGN) == SIG_DFL)
                (void) sigset(SIGINT, intr);
        if (sigset(SIGQUIT, SIG_IGN) == SIG_DFL)
                (void) sigset(SIGQUIT, intr);
        (void) sigset(SIGPIPE, intr);
        (void) sigset(SIGTERM, intr);

        /*
         * Parse options, record generic advice if any and create
         * rawadv_list from specific address advice.
         */

        while ((opt = getopt(argc, argv, "Flo:v")) != EOF) {
                switch (opt) {
                case 'o':
                        options = optarg;
                        while (*options != '\0') {
                                subopt = getsubopt(&options, suboptstr,
                                    &value);
                                switch (subopt) {
                                case AT_PRIVM:
                                case AT_HEAP:
                                case AT_SHARED:
                                case AT_STACK:
                                        at_map |= (1 << subopt);
                                        tmpadv = get_advice(value);
                                        set_advice(&generic_adv[subopt],
                                            tmpadv);
                                        break;
                                default:
                                        at_map |= (1 << AT_SEG);
                                        psaddr = parse_suboptions(value);
                                        if (psaddr == NULL) {
                                                usage();
                                        } else {
                                                insert_addr(&rawadv_list,
                                                    psaddr, YESDUPS);
                                        }
                                        break;
                                }
                        }
                        break;
                case 'v':
                        opt_verbose = 1;
                        break;
                case 'F':               /* force grabbing (no O_EXCL) */
                        Fflag = PGRAB_FORCE;
                        break;
                case 'l':               /* show unresolved link map names */
                        lflag = 1;
                        break;
                default:
                        usage();
                        break;
                }
        }

        argc -= optind;
        argv += optind;

        if (argc <= 0) {
                usage();
        }

        (void) proc_initstdio();

        /*
         * Iterate through all pid arguments, create new merged_list, maplist,
         * (and chopped_list if using verbose output) based on each process'
         * memory map.
         */

        while (!interrupt && argc-- > 0) {
                char *arg;
                int gcode;
                psinfo_t psinfo;

                (void) proc_flushstdio();

                if ((Pr = proc_arg_grab(arg = *argv++, PR_ARG_PIDS,
                    PGRAB_RETAIN | Fflag, &gcode)) == NULL) {
                        (void) fprintf(stderr,
                            gettext("%s: cannot examine %s: %s\n"),
                            progname, arg, Pgrab_error(gcode));
                        rc++;
                        continue;
                }


                addr_width =
                    (Pstatus(Pr)->pr_dmodel == PR_MODEL_LP64) ? 16 : 8;
                size_width =
                    (Pstatus(Pr)->pr_dmodel == PR_MODEL_LP64) ? 11 : 8;
                (void) memcpy(&psinfo, Ppsinfo(Pr), sizeof (psinfo_t));

                if (opt_verbose) {
                        proc_unctrl_psinfo(&psinfo);
                        (void) printf("%d:\t%.70s\n",
                            (int)psinfo.pr_pid, psinfo.pr_psargs);
                }

                /*
                 * Get mappings for a process unless it is a system process.
                 */
                if (!(Pstatus(Pr)->pr_flags & PR_ISSYS)) {
                        nstacks = psinfo.pr_nlwp * 2;
                        stacks = calloc(nstacks, sizeof (stacks[0]));
                        if (stacks != NULL) {
                                int n = 0;
                                (void) Plwp_iter(Pr, getstack, &n);
                                qsort(stacks, nstacks, sizeof (stacks[0]),
                                    cmpstacks);
                        }

                        if (Pgetauxval(Pr, AT_BASE) != -1L &&
                            Prd_agent(Pr) == NULL) {
                                (void) fprintf(stderr,
                                    gettext("%s: warning: "
                                    "librtld_db failed to initialize; "
                                    "shared library information will not "
                                    "be available\n"),
                                    progname);
                        }

                        comm_page = Pgetauxval(Pr, AT_SUN_COMMPAGE);

                        /*
                         * Create linked list of mappings for current process
                         * In addition, add generic advice and raw advice
                         * entries to merged_list.
                         * e.g. if rawadv_list contains:
                         *   [0x38000,0x3a000) = adv1
                         *   [0x3a000,0x3c000) = adv2
                         * and there is generic advice:
                         *   heap = adv3
                         * where heap corresponds to 0x38000, then merged_list
                         * will contain:
                         *   ... (include all other mappings from process)
                         *   [0x38000,0x3c000) = adv3
                         *   [0x38000,0x3a000) = adv1
                         *   [0x3a000,0x3c000) = adv2
                         *   ... (include all other mappings from process)
                         */
                        assert(merged_list == NULL);
                        maplist_head = maplist_tail = NULL;
                        rc += Pmapping_iter(Pr, (proc_map_f *)create_maplist,
                            NULL);

                        /*
                         * Apply advice by iterating through merged list
                         */
                        (void) apply_advice(&merged_list);

                        if (opt_verbose) {
                                assert(chopped_list == NULL);
                                /*
                                 * Create chopped_list from merged_list
                                 */
                                create_choplist(&chopped_list, merged_list);

                                /*
                                 * Iterate through maplist and output as
                                 * given by chopped_list
                                 */
                                print_advice(chopped_list, maplist_head);
                                delete_list(&chopped_list);
                        }

                        delete_list(&merged_list);

                        /*
                         * Clear maplist
                         */
                        pmapnode = maplist_head;
                        while (pmapnode) {
                                tempmapnode = pmapnode;
                                pmapnode = pmapnode->next;
                                free(tempmapnode);
                        }

                        if (stacks != NULL) {
                                free(stacks);
                                stacks = NULL;
                        }
                }

                Prelease(Pr, 0);
        }

        (void) proc_finistdio();

        return (rc);
}