--- pmap.1 +++ pmap.1 2009-05-11 12:14:59.377902682 +0200 @@ -1,39 +1,60 @@ -'\" t -.\" (The preceding line is a note to broken versions of man to tell -.\" them to pre-process this man page with tbl) -.\" Man page for pmap. -.\" Licensed under version 2 of the GNU General Public License. -.\" Written by Albert Cahalan. +.\" pmap.1 - manpage for the pmap(1) utility, part of procps .\" -.TH PMAP 1 "October 26, 2002" "Linux" "Linux User's Manual" +.\" Copyright (C) 2005 Robert Love +.\" Licensed under the terms of the GNU General Public License, v2 +.TH PMAP 1 "12 Oct 2005" "Linux" "Linux User's Manual" .SH NAME -pmap \- report memory map of a process +pmap \- display information about process memory mappings .SH SYNOPSIS -.nf -pmap [ -x | -d ] [ -q ] pids... -pmap -V -.fi +.BI "pmap [ \-d | \-q | \-h | \-V | \-A\ low,high ] " pid .SH DESCRIPTION -The pmap command reports the memory map of a process or processes. - -.SH "GENERAL OPTIONS" -.TS -l l l. --x extended Show the extended format. --d device Show the device format. --q quiet Do not display some header/footer lines. --V show version Displays version of program. -.TE +.BR pmap (1) +displays information about a process's memory mappings, such as its stack, +data segment, mapped files, and so on. +.P +The +.BR pmap (1) +utility will show, for each mapping of a given process, the starting byte +address in the process's address space, the size, the RSS (size of the mapping +in physical memory), the amount of dirty pages, the permission, the device node, +the offset, and the file backing the mapping, if any. +.P +As the last line of output, the +.BR pmap (1) +utility will tally up the total size of all mappings as well as show the +total size of writable/private mappings and of shared mappings. + +.SH OPTIONS +.TP +.B\-d, \-\^\-device +Display major and minor device numbers. +.TP +.B\-A, \-\-limit=low,high +Limit results to the given range. +.TP +.B\-q, \-\^\-quiet +Hide header and memory statistics. +.TP +.B\-h, \-\^\-help +Show pmap usage. +.TP +.B\-V, \-\^\-version +Display version information. + +.SH FILES +.IR /proc/pid/maps " and +.IR /proc/pid/smaps " \-\- memory mapping information" .SH "SEE ALSO" -ps(1) pgrep(1) +.BR ps (1), +.BR top (1), +.BR free (1), +.BR vmstat (1) -.SH STANDARDS -No standards apply, but pmap looks an awful lot like a SunOS command. +.SH AUTHORS +Written by Chris Rivera. -.SH AUTHOR -Albert Cahalan wrote pmap in 2002, and is the current -maintainer of the procps collection. Please send bug reports -to . +The procps package is maintained by Albert Calahan. Please send +bug reports to . --- pmap.c +++ pmap.c 2009-06-16 16:28:36.169902773 +0200 @@ -1,372 +1,405 @@ /* - * Copyright 2002 by Albert Cahalan; all rights reserved. - * This file may be used subject to the terms and conditions of the - * GNU Library General Public License Version 2, or any later version - * at your option, as published by the Free Software Foundation. - * This program 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 Library General Public License for more details. + * pmap - print the address space map of a process + * + * Chris Rivera + * Robert Love + * Werner Fink + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, v2, as + * published by the Free Software Foundation + * + * Copyright (C) 2003, 2005 Chris Rivera + * Copyright (C) 2009 Werner Fink */ #include +#include #include -#include -#include -#include -#include #include +#include +#include +#include #include -#include -#include - #include "proc/readproc.h" #include "proc/version.h" -#include "proc/escape.h" - -static void usage(void) NORETURN; -static void usage(void){ - fprintf(stderr, - "Usage: pmap [-x | -d] [-q] [-A low,high] pid...\n" - "-x show details\n" - "-d show offset and device number\n" - "-q quiet; less header/footer info\n" - "-V show the version number\n" - "-A limit results to the given range\n" - ); - exit(1); -} - - -static unsigned KLONG range_low; -static unsigned KLONG range_high = ~0ull; -static int V_option; -static int r_option; // ignored -- for SunOS compatibility -static int x_option; -static int d_option; -static int q_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) return; // failed; oh well - // attach - addr = shmat(shmid, NULL, SHM_RDONLY); - if(addr==(void*)-1) goto out_destroy; - - while(fgets(mapbuf, sizeof mapbuf, stdin)){ - char flags[32]; - char *tmp; // to clean up unprintables - unsigned KLONG start, end; - unsigned long long file_offset, inode; - unsigned dev_major, dev_minor; - sscanf(mapbuf,"%"KLF"x-%"KLF"x %31s %Lx %x:%x %Lu", &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; - } - } +#define BUFFERSIZE 4096 +#define OBJECTSIZE 1024 - if(shmdt(addr)) perror("shmdt"); - -out_destroy: - if(shmctl(shmid, IPC_RMID, NULL)) perror("IPC_RMID"); +struct smap { + unsigned long size; + unsigned long rss; + unsigned long pss; + unsigned long shared_clean; + unsigned long shared_dirty; + unsigned long private_clean; + unsigned long private_dirty; + unsigned long referenced; + unsigned long swap; +}; + +static unsigned long long range_low; +static unsigned long long range_high = ~0ULL; +static unsigned long mapped; +static unsigned long shared; +static unsigned long private; +static unsigned long rss; +static unsigned long pss; +static unsigned long dirty; +static unsigned long referenced; +static unsigned long swap; +static FILE *smaps_fp; +static int maj, min, patch, dopss, noref, doswap, dopage; +static long lbits; +#define BLK ((lbits==64)?" ":"") +#define WDT ((lbits==64)?16:8) + +static void usage(const char *cmd) +{ + fprintf(stderr, "usage: %s [options] pid\n", cmd); + fprintf(stderr, " -d, --device " + "display offset and device numbers\n"); + fprintf(stderr, " -q, --quiet " + "hide header and memory statistics\n"); + fprintf(stderr, " -A, --limit=low,high " + "limit results to the given range\n"); + fprintf(stderr, " -V, --version " + "display version information\n"); + fprintf(stderr, " -h, --help " + "display this help\n"); +} - return; +static int get_smap_data(struct smap *smap) +{ + unsigned long long data; + int assigned; + char buff[BUFFERSIZE]; + + /* get main line */ + if (!fgets(buff, BUFFERSIZE - 1, smaps_fp)) + return 1; + + assigned = sscanf(buff, "%llx-", &data); + if (assigned != 1) + return 1; + + /* get size */ + if (!fgets(buff, BUFFERSIZE, smaps_fp)) + return 1; + + assigned = sscanf(buff, "Size: %lld", &data); + if (assigned != 1) + return 1; + smap->size = data; + + /* get rss */ + if (!fgets(buff, BUFFERSIZE, smaps_fp)) + return 1; + + assigned = sscanf(buff, "Rss: %lld", &data); + if (assigned != 1) + return 1; + smap->rss = data; + rss += data; + + if (dopss) { + /* get pss */ + if (!fgets(buff, BUFFERSIZE, smaps_fp)) + return 1; + + assigned = sscanf(buff, "Pss: %lld", &data); + if (assigned != 1) + return 1; + smap->pss = data; + pss += data; + } + + /* get shared clean */ + if (!fgets(buff, BUFFERSIZE, smaps_fp)) + return 1; + + assigned = sscanf(buff, "Shared_Clean: %lld", &data); + if (assigned != 1) + return 1; + smap->shared_clean = data; + + /* get shared dirty */ + if (!fgets(buff, BUFFERSIZE, smaps_fp)) + return 1; + + assigned = sscanf(buff, "Shared_Dirty: %lld", &data); + if (assigned != 1) + return 1; + smap->shared_dirty = data; + dirty += data; + + /* get private clean */ + if (!fgets(buff, BUFFERSIZE, smaps_fp)) + return 1; + + assigned = sscanf(buff, "Private_Clean: %lld", &data); + if (assigned != 1) + return 1; + smap->private_clean = data; + + /* get private dirty */ + if (!fgets(buff, BUFFERSIZE, smaps_fp)) + return 1; + + assigned = sscanf(buff, "Private_Dirty: %lld", &data); + if (assigned != 1) + return 1; + smap->private_dirty = data; + dirty += data; + + if (noref) + goto out; + + /* get referenced */ + if (!fgets(buff, BUFFERSIZE, smaps_fp)) + return 1; + + assigned = sscanf(buff, "Referenced: %lld", &data); + if (assigned != 1) + return 1; + smap->referenced = data; + referenced += data; + + if (!doswap) + goto out; + + /* get swap */ + if (!fgets(buff, BUFFERSIZE, smaps_fp)) + return 1; + + assigned = sscanf(buff, "Swap: %lld", &data); + if (assigned != 1) + return 1; + smap->swap = data; + swap += data; + + if (!dopage) + goto out; + + if (!fgets(buff, BUFFERSIZE, smaps_fp)) + return 1; + if (!fgets(buff, BUFFERSIZE, smaps_fp)) + return 1; +out: + return 0; } +static void parse_line(pid_t pid, const char *line, int show_devices) +{ + unsigned long long low, high, size, offset; + unsigned long major, minor; + struct smap smap = { .rss = 0, .pss = 0 }; + int assigned; + char read_perm, write_perm, exec_perm, access_type; + char obj_buff[OBJECTSIZE] = "[anon]"; + + assigned = sscanf(line, "%llx-%llx %c%c%c%c %llx %lx:%lx %*u %" + STRINGIFY(OBJECTSIZE) "s", &low, &high, &read_perm, + &write_perm, &exec_perm, &access_type, &offset, &major, + &minor, obj_buff); + + if (assigned < 9) { + fprintf(stderr, "failed to parse /proc/%d/maps\n", pid); + exit(EXIT_FAILURE); + } + + size = high - low; + size /= 1024; + mapped += size; + + if (smaps_fp && get_smap_data(&smap)) { + fprintf(stderr, "failed to parse /proc/%d/smaps\n", pid); + exit(1); + } + + if (access_type == 's') + shared += size; + else if (access_type == 'p' && write_perm == 'w') + private += size; + + if(low > range_high) + return; + if(high < range_low) + return; + + printf("%0*llx %6lluK ", WDT, low, size); + + if (smaps_fp) { + printf("%6luK ", smap.rss); + if (dopss) + printf("%6luK ", smap.pss); + printf("%6luK ", smap.private_dirty + smap.shared_dirty); + if (doswap) + printf("%6luK ", smap.swap); + } -static const 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){ - const char *cp; + printf("%c%c%c%c ", read_perm, write_perm, exec_perm, access_type); - if(!dev_major && dev_minor==shm_minor && strstr(mapbuf,"/SYSV")){ - static char shmbuf[64]; - snprintf(shmbuf, sizeof shmbuf, " [ shmid=0x%Lx ]", 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; - return strrchr(cp,'/') + 1; // it WILL succeed - } - - cp = " [ anon ]"; - if( (p->start_stack >= addr) && (p->start_stack <= addr+len) ) cp = " [ stack ]"; - return cp; -} + if (show_devices) + printf("%0*llx %02lx:%02lx ", WDT, offset, major, minor); -static int one_proc(proc_t *p){ - char buf[32]; - char mapbuf[9600]; - char cmdbuf[512]; - unsigned long total_shared = 0ul; - unsigned long total_private_readonly = 0ul; - unsigned long total_private_writeable = 0ul; - - // 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(!freopen(buf, "r", stdin)) 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) printf("Address Kbytes RSS Anon Locked Mode Mapping\n"); - else printf("Address Kbytes RSS Anon Locked 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,stdin)){ - char flags[32]; - char *tmp; // to clean up unprintables - unsigned KLONG start, end, diff; - unsigned long long file_offset, inode; - unsigned dev_major, dev_minor; - sscanf(mapbuf,"%"KLF"x-%"KLF"x %31s %Lx %x:%x %Lu", &start, &end, flags, &file_offset, &dev_major, &dev_minor, &inode); - - if(start > range_high) - break; - if(end < range_low) - continue; - - 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){ - const char *cp = mapping_name(p, start, diff, mapbuf, 0, dev_major, dev_minor, inode); - printf( - (sizeof(KLONG)==8) - ? "%016"KLF"x %7lu - - - %s %s\n" - : "%08lx %7lu - - - %s %s\n", - start, - (unsigned long)(diff>>10), - flags, - cp - ); - } - 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 %016Lx %03x:%05x %s\n" - : "%08lx %7lu %s %016Lx %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 - - -\n", - (total_shared + total_private_writeable + total_private_readonly) >> 10 - ); - }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) printf(" total %16ldK\n", (total_shared + total_private_writeable + total_private_readonly) >> 10); - else printf(" total %8ldK\n", (total_shared + total_private_writeable + total_private_readonly) >> 10); - } - } - - return 0; + printf("%s\n", obj_buff); } +int main(int argc, char *argv[]) +{ + proc_t proc; + FILE *fp; + char path[PATH_MAX]; + char buff[BUFFERSIZE]; + int o, show_devices = 0, quiet = 0; + struct utsname uts; + pid_t pid; + + if (uname(&uts) < 0) { + fprintf(stderr, "error getting information about current kernel: %m\n"); + exit(EXIT_FAILURE); + } + sscanf(uts.release, "%d.%d.%d", &maj, &min, &patch); + + if ((maj > 2) || ((maj == 2) && ((min > 6) || ((min == 6) && (patch >= 30))))) + dopage++; + if ((maj > 2) || ((maj == 2) && ((min > 6) || ((min == 6) && (patch >= 27))))) + doswap++; + if ((maj > 2) || ((maj == 2) && ((min > 6) || ((min == 6) && (patch >= 25))))) + dopss++; + if ((maj < 2) || ((maj == 2) && ((min < 6) || ((min == 6) && (patch < 22))))) + noref++; + + if ((lbits = sysconf(_SC_LONG_BIT)) < 0) { + fprintf(stderr, "error getting information about current kernel: %m\n"); + exit(EXIT_FAILURE); + } + + struct option longopts[] = { + { "help", 0, NULL, 'h' }, + { "version", 0, NULL, 'V' }, + { "quiet", 0, NULL, 'q' }, + { "device", 0, NULL, 'd' }, + { "limit", 0, NULL, 'A' }, + { NULL, 0, NULL, 0 } + }; + + while ((o = getopt_long(argc, argv, "hqdA:V", longopts, NULL)) != -1) { + switch (o) { + case 'V': + display_version(); + return 0; + case 'q': + quiet = 1; + break; + case 'd': + show_devices = 1; + break; + case 'A': + if (!optarg || *optarg == 0) { + usage(argv[0]); + return 1; + } else { + char *low = optarg; + char *high = strchr(low, ','); + if (high) { + *high = '\0'; + high++; + } + if (low) + range_low = strtoull(low, &low, 16); + if (high) + range_high = strtoull(high, &high, 16); + if (*low || *high) { + usage(argv[0]); + return 1; + } + } + break; + case 'h': + usage(argv[0]); + return 0; + default: + usage(argv[0]); + return 1; + } + } + + if (argc - optind > 0) { + errno = 0; + pid = strtoul(argv[optind], NULL, 10); + if (errno) { + perror("strtoul"); + exit(EXIT_FAILURE); + } + } else { + usage(argv[0]); + exit(EXIT_FAILURE); + } + + if (!get_proc_stats(pid, &proc)) { + fprintf(stderr, "error getting process information for pid " + "%d from /proc/%d\n", pid, pid); + exit(EXIT_FAILURE); + } + + printf("%d: %s\n", pid, proc.cmd); + + snprintf(path, PATH_MAX, "/proc/%d/maps", pid); + fp = fopen(path, "r"); + if (!fp) { + perror("fopen"); + exit(EXIT_FAILURE); + } + + snprintf(path, PATH_MAX, "/proc/%d/smaps", pid); + smaps_fp = fopen(path, "r"); + + if (!quiet) { + printf("START%s SIZE ", BLK); + + if (smaps_fp) { + printf(" RSS "); + if (dopss) + printf(" PSS "); + printf(" DIRTY "); + if (doswap) + printf(" SWAP "); + } + + if (show_devices) + printf("PERM OFFSET DEVICE MAPPING\n"); + else + printf("PERM MAPPING\n"); + } + + while (fgets(buff, BUFFERSIZE - 1, fp)) + parse_line(pid, buff, show_devices); + + if (!quiet) { + if (smaps_fp) { + printf("Total:%s ", BLK); + printf(" %6luK", mapped); + printf(" %6luK", rss); + if (dopss) + printf(" %6luK", pss); + printf(" %6luK", dirty); + if (doswap) + printf(" %6luK", swap); + printf("\n\n"); + } else + printf("mapped: %luK ", mapped); + + if (noref) + printf("%luK writable-private, %luK readonly-private, and %luK shared\n", + private, mapped - private - shared, shared); + else + printf("%luK writable-private, %luK readonly-private, %luK shared, and %luK referenced\n", + private, mapped - private - shared, shared, referenced); + } -int main(int argc, char *argv[]){ - unsigned *pidlist; - unsigned count = 0; - PROCTAB* PT; - proc_t p; - int ret = 0; - - if(argc<2) usage(); - pidlist = malloc(sizeof(unsigned)*argc); // a bit more than needed perhaps - - while(*++argv){ - if(!strcmp("--version",*argv)){ - V_option++; - continue; - } - if(**argv=='-'){ - char *walk = *argv; - if(!walk[1]) usage(); - while(*++walk){ - switch(*walk){ - case 'V': - V_option++; - break; - case 'x': - x_option++; - break; - case 'r': - r_option++; - break; - case 'd': - d_option++; - break; - case 'q': - q_option++; - break; - case 'A':{ - char *arg1; - if(walk[1]){ - arg1 = walk+1; - walk += strlen(walk)-1; - }else{ - arg1 = *++argv; - if(!arg1) - usage(); - } - char *arg2 = strchr(arg1,','); - if(arg2) - *arg2 = '\0'; - arg2 = arg2 ? arg2++ : arg1; - - if(*arg1) - range_low = STRTOUKL(arg1,&arg1,16); - if(*arg2) - range_high = STRTOUKL(arg2,&arg2,16); - if(*arg1 || *arg2) - usage(); - } - break; - 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(); - } - } - }else{ - char *walk = *argv; - char *endp; - unsigned long pid; - if(!strncmp("/proc/",walk,6)){ - walk += 6; - // user allowed to do: pmap /proc/* - if(*walk<'0' || *walk>'9') continue; - } - if(*walk<'0' || *walk>'9') usage(); - pid = strtoul(walk, &endp, 0); - if(pid<1ul || pid>0x7ffffffful || *endp) usage(); - pidlist[count++] = pid; - } - } - - if( (x_option|V_option|r_option|d_option|q_option) >> 1 ) usage(); // dupes - if(V_option){ - if(count|x_option|r_option|d_option|q_option) usage(); - fprintf(stdout, "pmap (%s)\n", procps_version); - return 0; - } - if(count<1) usage(); // no processes - if(d_option && x_option) usage(); - - discover_shm_minor(); - - pidlist[count] = 0; // old libproc interface is zero-terminated - PT = openproc(PROC_FILLSTAT|PROC_FILLARG|PROC_PID, pidlist); - while(readproc(PT, &p)){ - ret |= one_proc(&p); - if(p.cmdline) free((void*)*p.cmdline); - count--; - } - closeproc(PT); - - if(count) ret |= 42; // didn't find all processes asked for - return ret; + return 0; }