1 #include <ctype.h>
2 #include <errno.h>
3 #include <fcntl.h>
4 #include <math.h>
5 #include <stddef.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <unistd.h>
10 
11 struct mapinfo {
12     mapinfo *next;
13     unsigned start;
14     unsigned end;
15     unsigned size;
16     unsigned rss;
17     unsigned pss;
18     unsigned shared_clean;
19     unsigned shared_dirty;
20     unsigned private_clean;
21     unsigned private_dirty;
22     unsigned swap;
23     int is_bss;
24     int count;
25     char name[1];
26 };
27 
28 static bool verbose = false;
29 static bool terse = false;
30 static bool addresses = false;
31 static bool quiet = false;
32 
is_library(const char * name)33 static int is_library(const char *name) {
34     int len = strlen(name);
35     return len >= 4 && name[0] == '/'
36             && name[len - 3] == '.' && name[len - 2] == 's' && name[len - 1] == 'o';
37 }
38 
39 // 6f000000-6f01e000 rwxp 00000000 00:0c 16389419   /android/lib/libcomposer.so
40 // 012345678901234567890123456789012345678901234567890123456789
41 // 0         1         2         3         4         5
42 
parse_header(const char * line,const mapinfo * prev,mapinfo ** mi)43 static int parse_header(const char* line, const mapinfo* prev, mapinfo** mi) {
44     unsigned long start;
45     unsigned long end;
46     char name[128];
47     int name_pos;
48     int is_bss = 0;
49 
50     if (sscanf(line, "%lx-%lx %*s %*x %*x:%*x %*d%n", &start, &end, &name_pos) != 2) {
51         *mi = NULL;
52         return -1;
53     }
54 
55     while (isspace(line[name_pos])) {
56         name_pos += 1;
57     }
58 
59     if (line[name_pos]) {
60         strlcpy(name, line + name_pos, sizeof(name));
61     } else {
62         if (prev && start == prev->end && is_library(prev->name)) {
63             // anonymous mappings immediately adjacent to shared libraries
64             // usually correspond to the library BSS segment, so we use the
65             // library's own name
66             strlcpy(name, prev->name, sizeof(name));
67             is_bss = 1;
68         } else {
69             strlcpy(name, "[anon]", sizeof(name));
70         }
71     }
72 
73     const int name_size = strlen(name) + 1;
74     struct mapinfo* info = reinterpret_cast<mapinfo*>(calloc(1, sizeof(mapinfo) + name_size));
75     if (info == NULL) {
76         fprintf(stderr, "out of memory\n");
77         exit(1);
78     }
79 
80     info->start = start;
81     info->end = end;
82     info->is_bss = is_bss;
83     info->count = 1;
84     strlcpy(info->name, name, name_size);
85 
86     *mi = info;
87     return 0;
88 }
89 
parse_field(mapinfo * mi,const char * line)90 static int parse_field(mapinfo* mi, const char* line) {
91     char field[64];
92     int len;
93 
94     if (sscanf(line, "%63s %n", field, &len) == 1
95             && *field && field[strlen(field) - 1] == ':') {
96         int size;
97         if (sscanf(line + len, "%d kB", &size) == 1) {
98             if (!strcmp(field, "Size:")) {
99                 mi->size = size;
100             } else if (!strcmp(field, "Rss:")) {
101                 mi->rss = size;
102             } else if (!strcmp(field, "Pss:")) {
103                 mi->pss = size;
104             } else if (!strcmp(field, "Shared_Clean:")) {
105                 mi->shared_clean = size;
106             } else if (!strcmp(field, "Shared_Dirty:")) {
107                 mi->shared_dirty = size;
108             } else if (!strcmp(field, "Private_Clean:")) {
109                 mi->private_clean = size;
110             } else if (!strcmp(field, "Private_Dirty:")) {
111                 mi->private_dirty = size;
112             } else if (!strcmp(field, "Swap:")) {
113                 mi->swap = size;
114             }
115         }
116         return 0;
117     }
118     return -1;
119 }
120 
order_before(const mapinfo * a,const mapinfo * b,int sort_by_address)121 static int order_before(const mapinfo *a, const mapinfo *b, int sort_by_address) {
122     if (sort_by_address) {
123         return a->start < b->start
124                 || (a->start == b->start && a->end < b->end);
125     } else {
126         return strcmp(a->name, b->name) < 0;
127     }
128 }
129 
enqueue_map(mapinfo ** head,mapinfo * map,int sort_by_address,int coalesce_by_name)130 static void enqueue_map(mapinfo **head, mapinfo *map, int sort_by_address, int coalesce_by_name) {
131     mapinfo *prev = NULL;
132     mapinfo *current = *head;
133 
134     if (!map) {
135         return;
136     }
137 
138     for (;;) {
139         if (current && coalesce_by_name && !strcmp(map->name, current->name)) {
140             current->size += map->size;
141             current->rss += map->rss;
142             current->pss += map->pss;
143             current->shared_clean += map->shared_clean;
144             current->shared_dirty += map->shared_dirty;
145             current->private_clean += map->private_clean;
146             current->private_dirty += map->private_dirty;
147             current->swap += map->swap;
148             current->is_bss &= map->is_bss;
149             current->count++;
150             free(map);
151             break;
152         }
153 
154         if (!current || order_before(map, current, sort_by_address)) {
155             if (prev) {
156                 prev->next = map;
157             } else {
158                 *head = map;
159             }
160             map->next = current;
161             break;
162         }
163 
164         prev = current;
165         current = current->next;
166     }
167 }
168 
load_maps(int pid,int sort_by_address,int coalesce_by_name)169 static mapinfo *load_maps(int pid, int sort_by_address, int coalesce_by_name)
170 {
171     char fn[128];
172     FILE *fp;
173     char line[1024];
174     mapinfo *head = NULL;
175     mapinfo *current = NULL;
176     int len;
177 
178     snprintf(fn, sizeof(fn), "/proc/%d/smaps", pid);
179     fp = fopen(fn, "r");
180     if (fp == 0) {
181         if (!quiet) fprintf(stderr, "cannot open /proc/%d/smaps: %s\n", pid, strerror(errno));
182         return NULL;
183     }
184 
185     while (fgets(line, sizeof(line), fp) != 0) {
186         len = strlen(line);
187         if (line[len - 1] == '\n') {
188             line[--len] = 0;
189         }
190 
191         if (current != NULL && !parse_field(current, line)) {
192             continue;
193         }
194 
195         mapinfo *next;
196         if (!parse_header(line, current, &next)) {
197             enqueue_map(&head, current, sort_by_address, coalesce_by_name);
198             current = next;
199             continue;
200         }
201 
202         fprintf(stderr, "warning: could not parse map info line: %s\n", line);
203     }
204 
205     enqueue_map(&head, current, sort_by_address, coalesce_by_name);
206 
207     fclose(fp);
208 
209     if (!head) {
210         if (!quiet) fprintf(stderr, "could not read /proc/%d/smaps\n", pid);
211         return NULL;
212     }
213 
214     return head;
215 }
216 
print_header()217 static void print_header()
218 {
219     const char *addr1 = addresses ? "   start      end " : "";
220     const char *addr2 = addresses ? "    addr     addr " : "";
221 
222     printf("%s virtual                     shared   shared  private  private\n", addr1);
223     printf("%s    size      RSS      PSS    clean    dirty    clean    dirty     swap ", addr2);
224     if (!verbose && !addresses) {
225         printf("   # ");
226     }
227     printf("object\n");
228 }
229 
print_divider()230 static void print_divider()
231 {
232     if (addresses) {
233         printf("-------- -------- ");
234     }
235     printf("-------- -------- -------- -------- -------- -------- -------- -------- ");
236     if (!verbose && !addresses) {
237         printf("---- ");
238     }
239     printf("------------------------------\n");
240 }
241 
print_mi(mapinfo * mi,bool total)242 static void print_mi(mapinfo *mi, bool total)
243 {
244     if (addresses) {
245         if (total) {
246             printf("                  ");
247         } else {
248             printf("%08x %08x ", mi->start, mi->end);
249         }
250     }
251     printf("%8d %8d %8d %8d %8d %8d %8d %8d ", mi->size,
252            mi->rss,
253            mi->pss,
254            mi->shared_clean, mi->shared_dirty,
255            mi->private_clean, mi->private_dirty, mi->swap);
256     if (!verbose && !addresses) {
257         printf("%4d ", mi->count);
258     }
259 }
260 
show_map(int pid)261 static int show_map(int pid)
262 {
263     mapinfo total;
264     memset(&total, 0, sizeof(total));
265 
266     mapinfo *milist = load_maps(pid, addresses, !verbose && !addresses);
267     if (milist == NULL) {
268         return quiet ? 0 : 1;
269     }
270 
271     print_header();
272     print_divider();
273 
274     for (mapinfo *mi = milist; mi;) {
275         mapinfo* last = mi;
276 
277         total.shared_clean += mi->shared_clean;
278         total.shared_dirty += mi->shared_dirty;
279         total.private_clean += mi->private_clean;
280         total.private_dirty += mi->private_dirty;
281         total.swap += mi->swap;
282         total.rss += mi->rss;
283         total.pss += mi->pss;
284         total.size += mi->size;
285         total.count += mi->count;
286 
287         if (terse && !mi->private_dirty) {
288             goto out;
289         }
290 
291         print_mi(mi, false);
292         printf("%s%s\n", mi->name, mi->is_bss ? " [bss]" : "");
293 
294 out:
295         mi = mi->next;
296         free(last);
297     }
298 
299     print_divider();
300     print_header();
301     print_divider();
302 
303     print_mi(&total, true);
304     printf("TOTAL\n");
305 
306     return 0;
307 }
308 
main(int argc,char * argv[])309 int main(int argc, char *argv[])
310 {
311     int usage = 1;
312     int result = 0;
313     int pid;
314     char *arg;
315     char *argend;
316 
317     signal(SIGPIPE, SIG_IGN);
318     for (argc--, argv++; argc > 0; argc--, argv++) {
319         arg = argv[0];
320         if (!strcmp(arg,"-v")) {
321             verbose = true;
322             continue;
323         }
324         if (!strcmp(arg,"-t")) {
325             terse = true;
326             continue;
327         }
328         if (!strcmp(arg,"-a")) {
329             addresses = true;
330             continue;
331         }
332         if (!strcmp(arg,"-q")) {
333             quiet = true;
334             continue;
335         }
336         if (argc != 1) {
337             fprintf(stderr, "too many arguments\n");
338             break;
339         }
340         pid = strtol(arg, &argend, 10);
341         if (*arg && !*argend) {
342             usage = 0;
343             if (show_map(pid)) {
344                 result = 1;
345             }
346             break;
347         }
348         fprintf(stderr, "unrecognized argument: %s\n", arg);
349         break;
350     }
351 
352     if (usage) {
353         fprintf(stderr,
354                 "showmap [-t] [-v] [-c] [-q] <pid>\n"
355                 "        -t = terse (show only items with private pages)\n"
356                 "        -v = verbose (don't coalesce maps with the same name)\n"
357                 "        -a = addresses (show virtual memory map)\n"
358                 "        -q = quiet (don't show error if map could not be read)\n"
359                 );
360         result = 1;
361     }
362 
363     return result;
364 }
365