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