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