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
11struct 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
28static bool verbose = false;
29static bool terse = false;
30static bool addresses = false;
31static bool quiet = false;
32
33static 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
43static 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
90static 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
121static 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
130static 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
169static 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
217static 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
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 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
261static 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
294out:
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
309int 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