/*
* 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
* 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.
*/
/*
* 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 <assert.h>
#include <libproc.h>
#include <libgen.h>
#include <signal.h>
#include "pmap_common.h"
#ifndef TEXT_DOMAIN /* should be defined by cc -D */
#endif
/*
* Round up the value to the nearest kilobyte
*/
#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 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.
*/
1 << MADV_SEQUENTIAL)
1 << MADV_ACCESS_MANY)
static int create_maplist(void *, const prmap_t *, const char *);
static char *advtostr(int);
static int lflag = 0;
static char *progname;
static char *suboptstr[] = {
"private",
"shared",
"heap",
"stack",
};
int at_map = 0;
typedef struct saddr_struct {
int adv;
} saddr_t;
static int apply_advice(saddr_t **);
static void set_advice(int *, int);
/*
* The segment address advice from the command line
*/
/*
* 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.
*/
/*
* 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
*
*/
typedef struct mapnode_struct {
int mtypes;
} 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
{
(*np)++;
}
(*np)++;
}
return (0);
}
/*
* Prints usage and exits
*/
static void
usage()
{
gettext("usage:\t%s [-o option[,option]] [-Flv] pid ...\n"),
progname);
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
{
/*
* 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).
*/
return (1 << MADV_ACCESS_DEFAULT);
return (1 << MADV_ACCESS_MANY);
return (1 << MADV_ACCESS_LWP);
return (1 << MADV_SEQUENTIAL);
return (1 << MADV_WILLNEED);
return (1 << MADV_DONTNEED);
return (1 << MADV_RANDOM);
return (1 << MADV_NORMAL);
return (1 << MADV_FREE);
return (1 << MADV_PURGE);
else {
usage();
return (-1);
}
}
/*
* Function to convert character size indicators into actual size
* (i.e., 123M => sz = 123 * 1024 * 1024)
*/
static size_t
{
return (0);
switch (**endptr) {
case 'E':
case 'e':
/* FALLTHRU */
case 'P':
case 'p':
/* FALLTHRU */
case 'T':
case 't':
/* FALLTHRU */
case 'G':
case 'g':
/* FALLTHRU */
case 'M':
case 'm':
/* FALLTHRU */
case 'K':
case 'k':
/* 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
{
return;
}
return;
}
/*
* primary level of comparison is by address; smaller addr 1st
* secondary level of comparison is by length; bigger length 1st
*/
break;
}
}
/*
* Deletes given element from list
*/
static void
{
return;
}
}
if (prev) {
}
}
/*
* Delete entire list
*/
static void
{
}
}
static saddr_t *
{
char *endptr;
/*
* This must (better) be a segment addr
*/
/*
* 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).
*/
gettext("%s: invalid option %s\n"),
usage();
} else {
/* init other fields */
/* skip past address */
/* check for length */
if (*value == ':') {
/* skip the ":" */
value++;
}
if (*endptr != '=') {
gettext("%s: invalid option %s\n"),
/*
* if improperly formatted, free mem, print usage, and
* exit Note: usage ends with a call to exit()
*/
usage();
}
/* skip the "=" */
}
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
{
int i;
if (interrupt)
return (0);
/*
* 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.
*/
else
}
}
/*
* Add raw advice that applies to this mapping to the merged_list
*/
/*
* Advance to point in rawadv_list that applies to this mapping
*/
/*
* Copy over to merged_list, check to see if size needs to be filled in
*/
/*
* 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.
*/
}
/*
* Put mapping into merged list with no advice, then
* check to see if any generic advice applies.
*/
break;
}
}
/*
* Add to linked list of mappings
*/
if (maplist_tail == NULL) {
} else {
}
return (0);
}
/*
* Traverse advice list and apply all applicable advice to each region
*/
static int
{
int i;
/*
* Save next pointer since element may be removed before
* we get a chance to advance psaddr.
*/
/*
* 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.
*/
for (i = MADV_NORMAL; i <= MADV_PURGE; i++) {
/*
* madvise(3C) call failed trying to
* apply advice output error and remove
* from advice list
*/
gettext("Error applying "
"advice (%s) to memory range "
"[%lx, %lx):\n"),
perror("madvise");
/*
* Clear this advice from the advice
* mask. If no more advice is given
* for this element, remove element
* from list.
*/
break;
}
}
}
}
}
return (0);
}
/*
* Set advice but keep mutual exclusive property of advice groupings
*/
static void
/*
* 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;
*combined_adv &= ~GRP1_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
{
/*
* Initialize the adv to -1 as an indicator for invalid
* elements in the chopped list (created from gaps between
* memory maps).
*/
/*
* Again, initialize to -1 as an indicatorfor invalid elements
*/
}
} else {
/*
* must be last element, now that we've calculated
* all segment lengths, we can remove this node
*/
break;
}
}
/*
* set_advice() will take care of conflicting
* advice by taking only the last advice
* applied for each of the 3 groups of advice.
*/
break;
}
}
}
/*
* Print advice in pmap style for verbose output
*/
static void
{
char *advice;
while (psaddr) {
/*
* Using indicator flag from create_choppedlist, we know
* which entries in the chopped_list are gaps and should
* not be printed.
*/
continue;
}
/*
* Print segment mapping and advice if there is any, or just a
* segment mapping.
*/
(void) printf("%.*lX %*uK %6s %s\t%s\n",
advice);
} else {
(void) printf("%.*lX %*uK %6s %s\n",
}
}
}
/*
* Call madvise(3c) in the context of the target process
*/
static int
{
}
static char *
{
/*
* rwxsR
*
* r - segment is readable
* w - segment is writable
* x - segment is executable
* s - segment is shared
* R - segment is mapped MAP_NORESERVE
*
*/
return (code_buf);
}
/*
* Convert advice to a string containing a commented list of applicable advice
*/
static char *
{
int i;
*buf = '\0';
for (i = MADV_NORMAL; i <= MADV_PURGE; i++) {
if (adv & (1 << i)) {
/*
* check if it's the first advice entry
*/
if (*buf == '\0')
"<= %s", advicestr[i]);
else
}
}
}
return (buf);
}
/*
* Handler for catching signals from terminal
*/
/* ARGSUSED */
static void
{
interrupt++;
}
int
{
int Fflag = 0;
int rc = 0;
int tmpadv;
(void) textdomain(TEXT_DOMAIN);
/*
* Get name of program for error messages
*/
/*
* 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 (;-)
*/
/*
* Parse options, record generic advice if any and create
* rawadv_list from specific address advice.
*/
switch (opt) {
case 'o':
while (*options != '\0') {
&value);
switch (subopt) {
case AT_PRIVM:
case AT_HEAP:
case AT_SHARED:
case AT_STACK:
tmpadv);
break;
default:
usage();
} else {
}
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;
}
}
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.
*/
char *arg;
int gcode;
(void) proc_flushstdio();
gettext("%s: cannot examine %s: %s\n"),
rc++;
continue;
}
if (opt_verbose) {
(void) printf("%d:\t%.70s\n",
}
/*
* Get mappings for a process unless it is a system process.
*/
int n = 0;
}
gettext("%s: warning: "
"librtld_db failed to initialize; "
"shared library information will not "
"be available\n"),
progname);
}
/*
* 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)
*/
NULL);
/*
* Apply advice by iterating through merged list
*/
(void) apply_advice(&merged_list);
if (opt_verbose) {
/*
* Create chopped_list from merged_list
*/
/*
* Iterate through maplist and output as
* given by chopped_list
*/
}
/*
* Clear maplist
*/
while (pmapnode) {
}
}
}
}
(void) proc_finistdio();
return (rc);
}