procps/pmap.c
Sami Kerola d5c760ee07 pmap: use only address start to range determination
Fix to an edge case.  When user defined begining of address range to
be at between two allocations the previous allocation which ended to
that address was included to printout.  After this commit one will
see only allocations that are within range definition.

$ pmap -A00007f4e0df08000,00007f4e0df08000 895
895:   bash
00007f4e0dd08000   2048K -----  /lib/libreadline.so.6.2
00007f4e0df08000      8K r----  /lib/libreadline.so.6.2
 total             2056K

Signed-off-by: Sami Kerola <kerolasa@iki.fi>
2012-03-04 12:13:18 +01:00

509 lines
12 KiB
C

/*
* pmap.c - print process memory mapping
* Copyright 2002 Albert Cahalan
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "c.h"
#include "nls.h"
#include "proc/escape.h"
#include "xalloc.h"
#include "proc/readproc.h"
#include "proc/version.h"
static void __attribute__ ((__noreturn__))
usage(FILE * out)
{
fputs(USAGE_HEADER, out);
fprintf(out,
_(" %s [options] pid [pid ...]\n"), program_invocation_short_name);
fputs(USAGE_OPTIONS, out);
fputs(_(" -x, --extended show details\n"
" -d, --device show the device format\n"
" -q, --quiet do not display header and footer\n"
" -A, --range=<low>[,<high>] limit results to the given range\n"), out);
fputs(USAGE_SEPARATOR, out);
fputs(USAGE_HELP, out);
fputs(USAGE_VERSION, out);
fprintf(out, USAGE_MAN_TAIL("pmap(1)"));
exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
}
static unsigned KLONG range_low;
static unsigned KLONG range_high = ~0ull;
static int d_option;
static int q_option;
static int x_option;
static unsigned shm_minor = ~0u;
static void discover_shm_minor(void)
{
void *addr;
int shmid;
char mapbuf[256];
if (!freopen("/proc/self/maps", "r", stdin))
return;
/* create */
shmid = shmget(IPC_PRIVATE, 42, IPC_CREAT | 0666);
if (shmid == -1)
/* failed; oh well */
return;
/* attach */
addr = shmat(shmid, NULL, SHM_RDONLY);
if (addr == (void *)-1)
goto out_destroy;
while (fgets(mapbuf, sizeof mapbuf, stdin)) {
char flags[32];
/* to clean up unprintables */
char *tmp;
unsigned KLONG start, end;
unsigned long long file_offset, inode;
unsigned dev_major, dev_minor;
sscanf(mapbuf, "%" KLF "x-%" KLF "x %31s %llx %x:%x %llu", &start,
&end, flags, &file_offset, &dev_major, &dev_minor,
&inode);
tmp = strchr(mapbuf, '\n');
if (tmp)
*tmp = '\0';
tmp = mapbuf;
while (*tmp) {
if (!isprint(*tmp))
*tmp = '?';
tmp++;
}
if (start > (unsigned long)addr)
continue;
if (dev_major)
continue;
if (flags[3] != 's')
continue;
if (strstr(mapbuf, "/SYSV")) {
shm_minor = dev_minor;
break;
}
}
if (shmdt(addr))
perror(_("shared memory detach"));
out_destroy:
if (shmctl(shmid, IPC_RMID, NULL))
perror(_("shared memory remove"));
return;
}
static char *mapping_name(proc_t * p, unsigned KLONG addr,
unsigned KLONG len, const char *mapbuf,
unsigned showpath, unsigned dev_major,
unsigned dev_minor, unsigned long long inode)
{
char *cp;
if (!dev_major && dev_minor == shm_minor && strstr(mapbuf, "/SYSV")) {
static char shmbuf[64];
snprintf(shmbuf, sizeof shmbuf, " [ shmid=0x%llx ]", inode);
return shmbuf;
}
cp = strrchr(mapbuf, '/');
if (cp) {
if (showpath)
return strchr(mapbuf, '/');
return cp[1] ? cp + 1 : cp;
}
cp = strchr(mapbuf, '/');
if (cp) {
if (showpath)
return cp;
/* it WILL succeed */
return strrchr(cp, '/') + 1;
}
cp = _(" [ anon ]");
if ((p->start_stack >= addr) && (p->start_stack <= addr + len))
cp = _(" [ stack ]");
return cp;
}
static int one_proc(proc_t * p)
{
char buf[32];
char mapbuf[9600];
char cmdbuf[512];
FILE *fp;
unsigned long total_shared = 0ul;
unsigned long total_private_readonly = 0ul;
unsigned long total_private_writeable = 0ul;
KLONG diff = 0;
const char *cp2 = NULL;
unsigned long long rss = 0ull;
unsigned long long private_dirty = 0ull;
unsigned long long shared_dirty = 0ull;
unsigned long long total_rss = 0ull;
unsigned long long total_private_dirty = 0ull;
unsigned long long total_shared_dirty = 0ull;
/* Overkill, but who knows what is proper? The "w" prog uses
* the tty width to determine this.
*/
int maxcmd = 0xfffff;
sprintf(buf, "/proc/%u/maps", p->tgid);
if ((fp = fopen(buf, "r")) == NULL)
return 1;
if (x_option) {
sprintf(buf, "/proc/%u/smaps", p->tgid);
if ((fp = freopen(buf, "r", fp)) == NULL)
return 1;
}
escape_command(cmdbuf, p, sizeof cmdbuf, &maxcmd,
ESC_ARGS | ESC_BRACKETS);
printf("%u: %s\n", p->tgid, cmdbuf);
if (!q_option && (x_option | d_option)) {
if (x_option) {
if (sizeof(KLONG) == 4)
/* Translation Hint: Please keep
* alignment of the following four
* headers intact. */
printf
(_("Address Kbytes RSS Dirty Mode Mapping\n"));
else
printf
(_("Address Kbytes RSS Dirty Mode Mapping\n"));
}
if (d_option) {
if (sizeof(KLONG) == 4)
printf
(_("Address Kbytes Mode Offset Device Mapping\n"));
else
printf
(_("Address Kbytes Mode Offset Device Mapping\n"));
}
}
while (fgets(mapbuf, sizeof mapbuf, fp)) {
char flags[32];
/* to clean up unprintables */
char *tmp;
unsigned KLONG start, end;
unsigned long long file_offset, inode;
unsigned dev_major, dev_minor;
unsigned long long smap_value;
char smap_key[20];
/* hex values are lower case or numeric, keys are upper */
if (mapbuf[0] >= 'A' && mapbuf[0] <= 'Z') {
/* Its a key */
if (sscanf
(mapbuf, "%20[^:]: %llu", smap_key,
&smap_value) == 2) {
if (strncmp("Rss", smap_key, 3) == 0) {
rss = smap_value;
total_rss += smap_value;
continue;
}
if (strncmp("Shared_Dirty", smap_key, 12) == 0) {
shared_dirty = smap_value;
total_shared_dirty += smap_value;
continue;
}
if (strncmp("Private_Dirty", smap_key, 13) == 0) {
private_dirty = smap_value;
total_private_dirty += smap_value;
continue;
}
if (strncmp("Swap", smap_key, 4) == 0) {
/*doesnt matter as long as last */
printf((sizeof(KLONG) == 8)
? "%016" KLF
"x %7lu %7llu %7llu %s %s\n" :
"%08lx %7lu %7llu %7llu %s %s\n",
start,
(unsigned long)(diff >> 10), rss,
(private_dirty + shared_dirty),
flags, cp2);
/* reset some counters */
rss = shared_dirty = private_dirty =
0ull;
diff = 0;
continue;
}
/* Other keys */
continue;
}
}
sscanf(mapbuf, "%" KLF "x-%" KLF "x %31s %llx %x:%x %llu", &start,
&end, flags, &file_offset, &dev_major, &dev_minor,
&inode);
if (end - 1 < range_low)
continue;
if (range_high < start)
break;
tmp = strchr(mapbuf, '\n');
if (tmp)
*tmp = '\0';
tmp = mapbuf;
while (*tmp) {
if (!isprint(*tmp))
*tmp = '?';
tmp++;
}
diff = end - start;
if (flags[3] == 's')
total_shared += diff;
if (flags[3] == 'p') {
flags[3] = '-';
if (flags[1] == 'w')
total_private_writeable += diff;
else
total_private_readonly += diff;
}
/* format used by Solaris 9 and procps-3.2.0+ an 'R'
* if swap not reserved (MAP_NORESERVE, SysV ISM
* shared mem, etc.)
*/
flags[4] = '-';
flags[5] = '\0';
if (x_option) {
cp2 =
mapping_name(p, start, diff, mapbuf, 0, dev_major,
dev_minor, inode);
/* printed with the keys */
continue;
}
if (d_option) {
const char *cp =
mapping_name(p, start, diff, mapbuf, 0, dev_major,
dev_minor, inode);
printf((sizeof(KLONG) == 8)
? "%016" KLF "x %7lu %s %016llx %03x:%05x %s\n"
: "%08lx %7lu %s %016llx %03x:%05x %s\n",
start,
(unsigned long)(diff >> 10),
flags, file_offset, dev_major, dev_minor, cp);
}
if (!x_option && !d_option) {
const char *cp =
mapping_name(p, start, diff, mapbuf, 1, dev_major,
dev_minor, inode);
printf((sizeof(KLONG) == 8)
? "%016" KLF "x %6luK %s %s\n"
: "%08lx %6luK %s %s\n",
start, (unsigned long)(diff >> 10), flags, cp);
}
}
if (!q_option) {
if (x_option) {
if (sizeof(KLONG) == 8) {
printf
("---------------- ------ ------ ------\n");
printf(_("total kB %15ld %7llu %7llu\n"),
(total_shared + total_private_writeable +
total_private_readonly) >> 10,
total_rss,
(total_shared_dirty +
total_private_dirty)
);
} else {
printf
("-------- ------- ------- ------- -------\n");
printf
(_("total kB %7ld - - -\n"),
(total_shared + total_private_writeable +
total_private_readonly) >> 10);
}
}
if (d_option) {
printf
(_("mapped: %ldK writeable/private: %ldK shared: %ldK\n"),
(total_shared + total_private_writeable +
total_private_readonly) >> 10,
total_private_writeable >> 10, total_shared >> 10);
}
if (!x_option && !d_option) {
if (sizeof(KLONG) == 8)
/* Translation Hint: keep total string length
* as 24 characters. Adjust %16 if needed*/
printf(_(" total %16ldK\n"),
(total_shared + total_private_writeable +
total_private_readonly) >> 10);
else
/* Translation Hint: keep total string length
* as 16 characters. Adjust %8 if needed*/
printf(_(" total %8ldK\n"),
(total_shared + total_private_writeable +
total_private_readonly) >> 10);
}
}
return 0;
}
static void range_arguments(char *optarg)
{
char *arg1;
char *arg2;
arg1 = xstrdup(optarg);
arg2 = strchr(arg1, ',');
if (arg2)
*arg2 = '\0';
if (arg2)
++arg2;
else
arg2 = arg1;
if (arg1 && *arg1)
range_low = STRTOUKL(arg1, &arg1, 16);
if (*arg2)
range_high = STRTOUKL(arg2, &arg2, 16);
if (arg1 && (*arg1 || *arg2))
xerrx(EXIT_FAILURE, "%s: '%s'", _("failed to parse argument"),
optarg);
}
int main(int argc, char **argv)
{
unsigned *pidlist;
unsigned count = 0;
PROCTAB *PT;
proc_t p;
int ret = 0, c;
static const struct option longopts[] = {
{"extended", no_argument, NULL, 'x'},
{"device", no_argument, NULL, 'd'},
{"quiet", no_argument, NULL, 'q'},
{"range", required_argument, NULL, 'A'},
{"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'V'},
{NULL, 0, NULL, 0}
};
program_invocation_name = program_invocation_short_name;
setlocale (LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
x_option = d_option = q_option = 0;
while ((c = getopt_long(argc, argv, "xrdqA:hV", longopts, NULL)) != -1)
switch (c) {
case 'x':
x_option = 1;
break;
case 'r':
xwarnx(_("option -r is ignored as SunOS compatibility"));
break;
case 'd':
d_option = 1;
break;
case 'q':
q_option = 1;
break;
case 'A':
range_arguments(optarg);
break;
case 'h':
usage(stdout);
case 'V':
printf(PROCPS_NG_VERSION);
return EXIT_SUCCESS;
case 'a': /* Sun prints anon/swap reservations */
case 'F': /* Sun forces hostile ptrace-like grab */
case 'l': /* Sun shows unresolved dynamic names */
case 'L': /* Sun shows lgroup info */
case 's': /* Sun shows page sizes */
case 'S': /* Sun shows swap reservations */
default:
usage(stderr);
}
argc -= optind;
argv += optind;
if (argc < 1)
xerrx(EXIT_FAILURE, _("argument missing"));
if (d_option && x_option)
xerrx(EXIT_FAILURE, _("options -d and -x cannot coexist"));
pidlist = xmalloc(sizeof(unsigned) * argc);
while (*argv) {
char *walk = *argv++;
char *endp;
unsigned long pid;
if (!strncmp("/proc/", walk, 6)) {
walk += 6;
/* user allowed to do: pmap /proc/PID */
if (*walk < '0' || *walk > '9')
continue;
}
if (*walk < '0' || *walk > '9')
usage(stderr);
pid = strtoul(walk, &endp, 0);
if (pid < 1ul || pid > 0x7ffffffful || *endp)
usage(stderr);
pidlist[count++] = pid;
}
discover_shm_minor();
memset(&p, '\0', sizeof(p));
/* old libproc interface is zero-terminated */
pidlist[count] = 0;
PT = openproc(PROC_FILLSTAT | PROC_FILLARG | PROC_PID, pidlist);
while (readproc(PT, &p)) {
ret |= one_proc(&p);
count--;
}
closeproc(PT);
if (count)
/* didn't find all processes asked for */
ret |= 42;
return ret;
}