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