pmap.c revision c2b5bce0bdd631275337b247a0eee409ce169f9b
/*
* 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.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdio.h>
#include <stdio_ext.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 <signal.h>
#include <sys/lgrp_user.h>
#include <libproc.h>
#include <libzonecfg.h>
#define KILOBYTE 1024
/*
* Round up the value to the nearest kilobyte
*/
/*
* The alignment should be a power of 2.
*/
struct totals {
};
/*
* -L option requires per-page information. The information is presented in an
* array of page_descr structures.
*/
typedef struct page_descr {
int pd_valid; /* valid page description if non-zero */
} page_descr_t;
/*
* Per-page information for a memory chunk.
* The meminfo(2) system call accepts up to MAX_MEMINFO_CNT pages at once.
* When we need to scan larger ranges we divide them in MAX_MEMINFO_CNT sized
* chunks. The chunk information is stored in the memory_chunk structure.
*/
typedef struct memory_chunk {
int page_index; /* Current page */
int page_count; /* Number of pages */
static volatile int interrupt;
typedef int proc_xmap_f(void *, const prxmap_t *, const char *, int, int);
int);
static int look_xmap_nopgsz(void *, const prxmap_t *, const char *,
int, int);
static int gather_map(void *, const prmap_t *, const char *);
static int gather_xmap(void *, const prxmap_t *, const char *, int, int);
static int iter_map(proc_map_f *, void *);
static int iter_xmap(proc_xmap_f *, void *);
static int perr(char *);
static void printK(long, int);
static int lflag = 0;
static int Lflag = 0;
static int aflag = 0;
/*
* The -A address range is represented as a pair of addresses
* <start_addr, end_addr>. Either one of these may be unspecified (set to
* INVALID_ADDRESS). If both are unspecified, no address range restrictions are
* in place.
*/
static int addr_width, size_width;
static char *command;
static char *procname;
static struct ps_prochandle *Pr;
static void intr(int);
typedef struct lwpstack {
} lwpstack_t;
typedef struct {
char *md_objname;
int md_doswap;
} mapdata_t;
static int map_count;
static int map_alloc;
#define MAX_TRIES 5
static int
{
(*np)++;
}
(*np)++;
}
return (0);
}
/*
* We compare the high memory addresses since stacks are faulted in from
* high memory addresses to low memory addresses, and our prmap_t
* structures identify only the range of addresses that have been faulted
* in so far.
*/
static int
{
if (a < b)
return (1);
if (a > b)
return (-1);
return (0);
}
int
{
int rc = 0;
int opt;
const char *bar8 = "-------";
const char *bar16 = "----------";
const char *bar;
char buf[128];
int mapfd;
command++;
else
switch (opt) {
case 'a': /* include shared mappings in -[xS] */
aflag = 1;
break;
case 'r': /* show reserved mappings */
rflag = 1;
break;
case 's': /* show hardware page sizes */
sflag = 1;
break;
case 'S': /* show swap reservations */
Sflag = 1;
break;
case 'x': /* show extended mappings */
xflag = 1;
break;
case 'l': /* show unresolved link map names */
lflag = 1;
break;
case 'L': /* show lgroup information */
Lflag = 1;
break;
case 'F': /* force grabbing (no O_EXCL) */
Fflag = PGRAB_FORCE;
break;
case 'A':
!= 0)
errflg++;
break;
default:
errflg = 1;
break;
}
}
errflg = 1;
}
"usage:\t%s [-rslF] [-A start[,end]] { pid | core } ...\n",
command);
"\t\t(report process address maps)\n");
"\t%s -L [-rslF] [-A start[,end]] pid ...\n", command);
"\t\t(report process address maps lgroups mappings)\n");
"\t%s -x [-aslF] [-A start[,end]] pid ...\n", command);
"\t%s -S [-alF] [-A start[,end]] { pid | core } ...\n",
command);
"\t\t(show swap reservations)\n\n");
"\t-a: include shared mappings in -[xS] summary\n");
"\t-r: show reserved address maps\n");
"\t-s: show hardware page sizes\n");
"\t-l: show unresolved dynamic linker map names\n");
"\t-F: force grabbing of the target process\n");
"\t-L: show lgroup mappings\n");
"\t-A start,end: limit output to the specified range\n");
return (2);
}
/*
* Make sure we'll have enough file descriptors to handle a target
* that has many many mappings.
*/
}
while (argc-- > 0) {
char *arg;
int gcode;
int tries = 0;
int prg_gflags = PGRAB_RDONLY;
int prr_flags = 0;
if (Lflag) {
}
rc++;
continue;
}
"examine %s: lost control of "
rc++;
continue;
}
} else {
mapfd = -1;
}
map_count = 0;
(void) printf("core '%s' of %d:\t%.70s\n",
(void) printf(" -%c option is not compatible "
rc++;
continue;
}
} else {
(void) printf("%d:\t%.70s\n",
}
if (Lflag) {
/*
* The implementation of -L option creates an agent LWP
* in the target process address space. The agent LWP
* issues meminfo(2) system calls on behalf of the
* target process. If we are interrupted prematurely,
* the target process remains in the stopped state with
* the agent still attached to it. To prevent such
* situation we catch signals from terminal and
* terminate gracefully.
*/
}
struct totals t;
/*
* Since we're grabbing the process readonly, we need
* to make sure the address space doesn't change during
* execution.
*/
"examine %s: address space is "
continue;
}
"examine %s: lost control of "
continue;
}
}
int n = 0;
}
(void) memset(&t, 0, sizeof (t));
"librtld_db failed to initialize; "
"shared library information will not be "
"available\n", command);
}
/*
* Gather data
*/
if (xflag)
else if (Sflag)
else {
if (rflag)
NULL);
else if (sflag)
NULL, 0);
else
NULL);
}
/*
* Ensure mappings are consistent.
*/
}
goto again;
}
}
/*
* Display data.
*/
if (xflag) {
(void) printf("%*s%*s%*s%*s%*s "
"%sMode Mapped File\n",
addr_width, "Address",
size_width, "Kbytes",
size_width, "RSS",
size_width, "Anon",
size_width, "Locked",
look_xmap_nopgsz, &t);
(void) printf("%s%s %s %s %s %s\n",
" " : "");
(void) printf("\n");
} else if (Sflag) {
(void) printf("%*s%*s%*s Mode"
" Mapped File\n",
addr_width, "Address",
size_width, "Kbytes",
size_width, "Swap");
(void) printf("%s%s %s %s\n",
" " : "");
(void) printf("\n");
} else {
if (rflag) {
} else if (sflag) {
if (Lflag) {
(void) printf("%*s %*s %4s"
" %-6s %s %s\n",
addr_width, "Address",
"Bytes", "Pgsz", "Mode ",
"Lgrp", "Mapped File");
} else {
(void) printf("%*s %*s %4s"
" %-6s %s\n",
addr_width, "Address",
"Bytes", "Pgsz", "Mode ",
"Mapped File");
}
} else {
}
(void) printf(" %stotal %*luK\n",
addr_width == 16 ?
" " : "",
size_width, t.total_size);
}
}
}
if (mapfd != -1)
}
return (rc);
}
static char *
{
char fname[100];
int len;
char zname[ZONENAME_MAX];
return (buf);
if (lflag)
return (buf);
return (buf);
}
/*
* If the target is in a non-global zone, attempt to prepend
* the zone path in order to give the global-zone caller the
* real path to the file.
*/
void *dlhdl;
if (((dlhdl =
return (NULL);
== Z_OK) {
return (NULL);
}
}
}
return (buf);
}
}
if (len >= 0) {
return (buf);
} else { /* there is no path and readlink() error */
"dev:%lu,%lu ino:%lu",
return (buf);
}
}
}
return (NULL);
}
static char *
{
if (shmid == -1)
else
if (shmid == -1)
else
} else {
if (nstacks > 0 &&
"altstack" : "stack",
stk->lwps_lwpid);
} else {
return (NULL);
}
}
return (name);
}
static int
{
ssize_t n;
if (mapfd >= 0)
}
return (perr("read rmap"));
}
return (rc);
}
}
return (0);
}
static int
{
ssize_t n;
if (mapfd >= 0)
}
nmap *= 2;
return (perr("read xmap"));
}
nmap *= 2;
goto again;
}
return (rc);
}
}
/*
* Mark the last element.
*/
if (map_count > 0)
return (0);
}
/*ARGSUSED*/
static int
{
/*
* 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.
*/
}
}
/*
* Adjust the address range if -A is specified.
*/
&vaddr, &segment_end);
if (size == 0)
return (0);
if (!Lflag) {
/*
* Display the whole mapping
*/
"%.*lX %*luK %-6s %s\n" :
"%.*lX %*luK %s\n",
t->total_size += size;
return (0);
}
/*
* We need to display lgroups backing physical memory, so we break the
* segment into individual pages and coalesce pages with the same lgroup
* into one "segment".
*/
/*
* Initialize address descriptions for the mapping.
*/
size = 0;
/*
* Walk mapping (page by page) and display contiguous ranges of memory
* allocated to same lgroup.
*/
do {
/*
* Get contiguous region of memory starting from vaddr allocated
* from the same lgroup.
*/
"%.*lX %*luK %s %s\n",
vaddr += size_contig;
size += size_contig;
/* Update the total size */
return (0);
}
static void
{
if (value == 0)
else
}
static const char *
{
static char buf[32];
if (pagesize == 0) {
return ("-"); /* no underlying HAT mapping */
}
else
} else
return (buf);
}
/*ARGSUSED*/
static int
const char *object_name,
{
const char *format;
/*
* 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.
*/
}
}
/*
* Adjust the address range if -A is specified.
*/
&vaddr, &segment_end);
if (size == 0)
return (0);
if (!Lflag) {
/*
* Display the whole mapping
*/
format = "%.*lX %*luK %4s %-6s %s\n";
else
format = "%.*lX %*luK %4s %s\n";
t->total_size += size;
return (0);
}
format = "%.*lX %*luK %4s %-6s%s %s\n";
else
format = "%.*lX %*luK %4s%s %s\n";
/*
* We need to display lgroups backing physical memory, so we break the
* segment into individual pages and coalesce pages with the same lgroup
* into one "segment".
*/
/*
* Initialize address descriptions for the mapping.
*/
size = 0;
/*
* Walk mapping (page by page) and display contiguous ranges of memory
* allocated to same lgroup.
*/
do {
/*
* Get contiguous region of memory starting from vaddr allocated
* from the same lgroup.
*/
vaddr += size_contig;
size += size_contig;
return (0);
}
((x)->pr_anon) : 0)
/*ARGSUSED*/
static int
const char *object_name,
{
char *ln;
/*
* 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.
*/
}
}
return (0);
}
/*ARGSUSED*/
static int
look_xmap_nopgsz(void *data,
const char *object_name,
{
char *ln;
static uintptr_t prev_vaddr;
static offset_t prev_offset;
static int prev_mflags;
static char *prev_lname;
static char prev_mname[PATH_MAX];
static ulong_t prev_locked;
int merged = 0;
static int first = 1;
int kperpage;
/*
* Calculate swap reservations
*/
/* Swap reserved for entire non-ism SHM */
}
/* Swap reserved on fault for each anon page */
/* Swap reserve for entire writable segment */
}
/*
* 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 (first == 1) {
first = 0;
prev_lname = NULL;
} else {
}
if (last == 0) {
return (0);
}
merged = 1;
((prev_mflags & MA_ISM) ||
if (last == 0) {
return (0);
}
merged = 1;
}
if (doswap)
else {
}
if (last == 0) {
prev_lname = NULL;
} else {
}
} else if (merged == 0) {
if (doswap)
else {
}
}
if (last != 0)
first = 1;
return (0);
}
static int
perr(char *s)
{
if (s)
else
s = procname;
perror(s);
return (1);
}
static char *
{
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
*
*/
return (str);
}
static mapdata_t *
nextmap(void)
{
int next;
if (map_alloc == 0)
next = 16;
else
(void) perr("failed to allocate maps");
exit(1);
}
}
}
/*ARGSUSED*/
static int
{
/* Skip mappings which are outside the range specified by -A */
return (0);
return (0);
}
/*ARGSUSED*/
static int
{
/* Skip mappings which are outside the range specified by -A */
return (0);
return (0);
}
static int
{
int i;
int ret;
for (i = 0; i < map_count; i++) {
if (interrupt)
break;
maps[i].md_objname)) != 0)
return (ret);
}
return (0);
}
static int
{
int i;
int ret;
for (i = 0; i < map_count; i++) {
if (interrupt)
break;
return (ret);
}
return (0);
}
/*
* Convert lgroup ID to string.
* returns dash when lgroup ID is invalid.
*/
static char *
{
static char lgrp_buf[20];
return (str);
}
/*
* Parse address range specification for -A option.
* The address range may have the following forms:
*
* address
* start and end is set to address
* address,
* start is set to address, end is set to INVALID_ADDRESS
* ,address
* start is set to 0, end is set to address
* address1,address2
* start is set to address1, end is set to address2
*
*/
static int
{
/*
* Comma is present. If there is nothing after comma, the end
* remains set at INVALID_ADDRESS. Otherwise it is set to the
* value after comma.
*/
*endp = '\0';
endp++;
return (1);
}
/*
* Read the start address, if it is specified. If the address is
* missing, start will be set to INVALID_ADDRESS.
*/
return (1);
}
/* If there is no comma, end becomes equal to start */
e = s;
/*
* ,end implies 0..end range
*/
if (e != INVALID_ADDRESS && s == INVALID_ADDRESS)
s = 0;
/* Return error if neither start nor end address were specified */
return (! (s != INVALID_ADDRESS || e != INVALID_ADDRESS));
}
/*
* Check whether any portion of [start, end] segment is within the
* [start_addr, end_addr] range.
*
* Return values:
* 0 - address is outside the range
* 1 - address is within the range
*/
static int
{
int rc = 1;
/*
* Nothing to do if there is no address range specified with -A
*/
/* The segment end is below the range start */
if ((start_addr != INVALID_ADDRESS) &&
rc = 0;
/* The segment start is above the range end */
if ((end_addr != INVALID_ADDRESS) &&
rc = 0;
}
return (rc);
}
/*
* Returns an intersection of the [start, end] interval and the range specified
* by -A flag [start_addr, end_addr]. Unspecified parts of the address range
* have value INVALID_ADDRESS.
*
* The start_addr address is rounded down to the beginning of page and end_addr
* is rounded up to the end of page.
*
* Returns the size of the resulting interval or zero if the interval is empty
* or invalid.
*/
static size_t
{
/*
* Round down the lower address of the range to the beginning of page.
*/
if (start_addr == INVALID_ADDRESS) {
/*
* No start_addr specified by -A, the lower part of the interval
* does not change.
*/
} else {
/*
* If end address is outside the range, return an empty
* interval
*/
return (0);
}
/*
* The adjusted start address is the maximum of requested start
* and the aligned start_addr of the -A range.
*/
}
/*
* Round up the higher address of the range to the end of page.
*/
if (end_addr == INVALID_ADDRESS) {
/*
* No end_addr specified by -A, the upper part of the interval
* does not change.
*/
} else {
/*
* If only one address is specified and it is the beginning of a
* segment, get information about the whole segment. This
* function is called once per segment and the 'end' argument is
* always the end of a segment, so just use the 'end' value.
*/
end :
/*
* If start address is outside the range, return an empty
* interval
*/
return (0);
}
/*
* The adjusted end address is the minimum of requested end
* and the aligned end_addr of the -A range.
*/
}
/*
* Make sure that the resulting interval is legal.
*/
/* Return the size of the interval */
}
/*
* Initialize memory_info data structure with information about a new segment.
*/
static void
{
chunk->page_index = 0;
}
/*
* Create a new chunk of addresses starting from vaddr.
* Pass the whole chunk to pr_meminfo to collect lgroup and page size
* information for each page in the chunk.
*/
static void
{
int i, j, rc;
/*
* Fill in MAX_MEMINFO_CNT wotrh of pages starting from vaddr. Also,
* copy starting address of each page to inaddr array for pr_meminfo.
*/
pdp->pd_pagesize = 0;
}
/* Mark the number of entries in the chunk and the last address */
chunk->page_count = i;
if (interrupt)
return;
/* Call meminfo for all collected addresses */
if (rc < 0) {
(void) perr("can not get memory information");
return;
}
/* Verify validity of each result and fill in the addrs array */
/* Skip invalid address pointers */
if ((*valptr & 1) == 0) {
continue;
}
/* Is lgroup information available? */
if ((*valptr & 2) != 0) {
}
/* Is page size informaion available? */
if ((*valptr & 4) != 0) {
}
}
}
/*
* Starting from address 'vaddr' find the region with pages allocated from the
* same lgroup.
*
* Arguments:
* mchunk Initialized memory chunk structure
* vaddr Starting address of the region
* maxaddr Upper bound of the region
* pagesize Default page size to use
* ret_lgrp On exit contains the lgroup ID of all pages in the
* region.
*
* Returns:
* Size of the contiguous region in bytes
* The lgroup ID of all pages in the region in ret_lgrp argument.
*/
static size_t
{
size_t size_contig = 0;
/* Set both lgroup IDs to the lgroup of the first page */
/*
* Starting from vaddr, walk page by page until either the end
* of the segment is reached or a page is allocated from a different
* lgroup. Also stop if interrupted from keyboard.
*/
/*
* Get lgroup ID and the page size of the current page.
*/
/* If there is no page size information, use the default */
if (psz == 0)
/*
* This page belongs to the contiguous region.
* Increase the region size and advance to the new page.
*/
size_contig += psz;
}
}
/* Return the region lgroup ID and the size */
return (size_contig);
}
/*
* Given a virtual address, return its lgroup and page size. If there is meminfo
* information for an address, use it, otherwise shift the chunk window to the
* vaddr and create a new chunk with known meminfo information.
*/
static lgrp_id_t
{
int i;
if (interrupt)
return (0);
/*
* Is there information about this address? If not, create a new chunk
* starting from vaddr and apply pr_meminfo() to the whole chunk.
*/
/*
* This address is outside the chunk, get the new chunk and
* collect meminfo information for it.
*/
}
/*
* Find information about the address.
*/
/*
* Override page size information if it is
* present.
*/
if (pdp->pd_pagesize > 0)
}
break;
}
}
/*
* Remember where we ended - the next search will start here.
* We can query for the lgrp for the same address again, so do not
* advance index past the current value.
*/
chunk->page_index = i;
return (lgrp);
}
/* ARGSUSED */
static void
{
interrupt = 1;
}