]> git.itanic.dy.fi Git - scan-pagemap/blob - parse.c
a4b878a3b85e0006de7b4fefbcf8cd0ae2f277ca
[scan-pagemap] / parse.c
1 #include <sys/types.h>
2 #include <dirent.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <errno.h>
7 #include <libgen.h>
8
9 #include "parse.h"
10 #include "pagemap.h"
11
12 static struct maps_list *alloc_maplist(void)
13 {
14         struct maps_list *map;
15
16         map = malloc(sizeof *map);
17         if (map == NULL)
18                 goto err;
19
20         memset(map, 0, sizeof(*map));
21         INIT_LIST_HEAD(&map->list);
22 err:
23         return map;
24 }
25
26 static struct maps *alloc_map(void)
27 {
28         struct maps *map;
29
30         map = malloc(sizeof *map);
31         if (map == NULL)
32                 goto err;
33
34         memset(map, 0, sizeof(*map));
35         INIT_LIST_HEAD(&map->list);
36 err:
37         return map;
38 }
39
40 static struct maps *parse_maps(FILE *file, int pid, int tid)
41 {
42         struct maps *the_map = NULL;
43         char line[1024];
44         int ret;
45
46         while (fgets(line, sizeof(line), file)) {
47                 struct maps *map = alloc_map();
48                 unsigned long start, end;
49                 char name[1024];
50
51                 if (map == NULL)
52                         return 0;
53
54                 if (the_map == NULL)
55                         the_map = map;
56
57                 ret = sscanf(line, "%lx-%lx %*s %*s %*s %*s %s",
58                              &start, &end, name);
59
60                 if (ret < 2) {
61                         printf("Error reading input: %s\n", line);
62                         break;
63                 }
64
65                 map->start = start;
66                 map->end = end;
67                 map->size = end - start;
68                 map->pid = pid;
69                 map->tid = tid;
70
71                 if (ret >= 3)
72                         strncpy(map->name, name, sizeof(map->name));
73
74                 list_add_tail(&map->list, &the_map->list);
75         }
76
77         return the_map;
78 }
79
80 static void clear_pageframe(struct pageframe *pf)
81 {
82         memset(pf, 0, sizeof(*pf));
83 }
84
85 static struct pageframe *alloc_pageframe(void)
86 {
87         struct pageframe *pageframe;
88
89         pageframe = malloc(sizeof *pageframe);
90         if (pageframe == NULL)
91                 goto err;
92
93         clear_pageframe(pageframe);
94         INIT_LIST_HEAD(&pageframe->ml.list);
95 err:
96         return pageframe;
97 }
98
99 #define BITRANGE(first, last) (((2ll << (last - first)) - 1) << first)
100
101 static void pageframe_to_struct(unsigned long long p, struct pageframe *pf)
102 {
103         /* Refer Documentation/vm/pagemap.txt for the format */
104         pf->page_present = !!(BITRANGE(63, 63) & p);
105         pf->page_swapped = !!(BITRANGE(62, 62) & p);
106         pf->page_shift   =   (BITRANGE(55, 60) & p) >> 55;
107         pf->pfn          =   (BITRANGE(0, 54) & p);
108         pf->swap_type    =   (BITRANGE(0, 4) & p);
109         pf->swap_offset  =   (BITRANGE(5, 54) & p) >> 5;
110 #if 0
111         printf("pfn: %lx shift: %d present: %d swapped %d\n",
112                 pf->pfn, pf->page_shift, pf->page_present, pf->page_swapped);
113 #endif
114 }
115
116 static int compare_pageframe(struct bintree *at, struct bintree *bt)
117 {
118         struct pageframe *a, *b;
119         a = tree_to_pageframe(at);
120         b = tree_to_pageframe(bt);
121
122         return a->pfn - b->pfn;
123 }
124
125 struct bintree_ops pageframe_ops = {
126         .compare = compare_pageframe,
127 };
128
129 static int read_cmdline(int pid, int tid, char *cmdline, size_t len)
130 {
131         FILE *file;
132         char path[512];
133         int ret;
134
135         snprintf(path, sizeof(path), "/proc/%d/task/%d/cmdline", pid, tid);
136         file = fopen(path, "rb");
137
138         if (!file)
139                 return -1;
140
141         ret = fread(cmdline, 1, len, file);
142         if (ret > 0)
143                 cmdline[ret - 1] = 0;
144         fclose(file);
145
146         return ret > 0 ? 0 : -1;
147 }
148
149 static char *get_name_by_pid(int pid)
150 {
151         static int last_pid;
152         static char cmdline[128];
153         static char *bname;
154
155         if (last_pid == pid)
156                 return bname;
157
158         if (read_cmdline(pid, pid, cmdline, sizeof(cmdline))) {
159                 bname = NULL;
160                 return NULL;
161         }
162
163         bname = basename(cmdline);
164
165         last_pid = pid;
166         return bname;
167 }
168
169 static int should_scan_process(struct parse_opts *opts, struct process *process)
170 {
171         int match = 0;
172         char *name;
173
174         if (opts->parse_mask & PARSE_PROCESS_NAME) {
175                 name = get_name_by_pid(process->pid);
176                 if (!strcmp(opts->name, name ? name : ""))
177                         match = 1;
178         }
179
180         if (opts->parse_mask & PARSE_PID) {
181                 if (opts->pid == process->pid)
182                         match = 1;
183         }
184
185         if (opts->parse_mask & PARSE_MAP_NAME)
186                 match = 1;
187
188         if (opts->parse_mask & PARSE_NOADD_TREE)
189                 match = !match;
190
191         return match;
192 }
193
194 static int should_scan_mapping(struct parse_opts *opts, struct maps *map)
195 {
196         int match = 0;
197
198         if (opts->parse_mask & PARSE_MAP_NAME) {
199                 if (!strcmp(opts->name, map->name))
200                         match = 1;
201
202                 if (opts->parse_mask & PARSE_NOADD_TREE)
203                         match = !match;
204         } else
205                 match = 1;
206
207         return match;
208 }
209
210 static int should_add_to_tree(struct parse_opts *opts, struct pageframe *pf,
211                 struct maps *map)
212 {
213         if (opts->parse_mask & PARSE_NOADD_TREE)
214                 return 0;
215
216         return 1;
217 }
218
219 /* Read data from the /proc/pid/pagemap file */
220 static int parse_pageframe(FILE *file, struct pageframe *pf_tree,
221                         struct maps *maps, struct parse_opts *opts)
222 {
223         struct maps *map;
224         struct maps_list *tmp;
225         struct pageframe *match, *pageframe = NULL;
226         long start, len, i;
227         unsigned long long pf[10240];
228         int ret, error;
229
230         if (maps == NULL)
231                 return 0;
232
233         /* Go through the list of allocated memory areas */
234         list_for_each_entry(map, &maps->list, list) {
235                 start = map->start >> (PAGE_SHIFT - 3);
236                 len = map->size >> (PAGE_SHIFT);
237
238                 if (!should_scan_mapping(opts, map))
239                         continue;
240
241                 ret = fseek(file, start, SEEK_SET);
242                 if (ret) {
243                         error = errno;
244                         fprintf(stderr, "Error seeking to %lx: %s\n", start,
245                                 strerror(error));
246                         continue;
247                 }
248
249                 for (i = 0; i < len; i++) {
250                         if (!ret) {
251                                 ret = fread(&pf, 1,
252                                         MIN(sizeof(pf), (len - i) * 8), file);
253                         }
254                         if (ret < 0) {
255                                 error = errno;
256                                 continue;
257                         }
258                         if (!pageframe)
259                                 pageframe = alloc_pageframe();
260                         ret -= sizeof(pf[0]);
261
262                         /* ignore unused pages */
263                         if (!pf[ret / sizeof(pf[0])])
264                                 continue;
265
266                         pageframe_to_struct(pf[ret / sizeof(pf[0])], pageframe);
267
268                         /* ignore unused pages */
269                         if (!(pageframe->page_swapped ||
270                                         pageframe->page_present))
271                                 continue;
272
273                         if (should_add_to_tree(opts, pageframe, map)) {
274                                 match = tree_to_pageframe(
275                                         bintree_add(&pf_tree->tree,
276                                                 &pageframe->tree,
277                                                 &pageframe_ops));
278                         } else {
279                                 match = tree_to_pageframe(
280                                         bintree_find(&pf_tree->tree,
281                                                 &pageframe->tree,
282                                                 &pageframe_ops));
283                         }
284
285                         if (match == NULL)
286                                 continue;
287
288                         if (match == pageframe)
289                                 pageframe = NULL;
290
291                         match->refcount++;
292                         /*
293                          * Add a link from the physical page to this
294                          * process's page map
295                          */
296                         tmp = alloc_maplist();
297                         tmp->map = map;
298                         list_add(&tmp->list, &match->ml.list);
299
300                         if (match->page_present)
301                                 map->pages_present++;
302                         else if (match->page_swapped)
303                                 map->pages_swapped++;
304                 }
305         }
306
307         return 0;
308 }
309
310 static int read_pageframe(int pid, int tid, struct pageframe *pageframe,
311                         struct process *process_list, struct parse_opts *opts)
312 {
313         struct maps *maps;
314         struct process *process;
315         FILE *file;
316         char path[512];
317
318         process = malloc(sizeof(*process));
319         memset(process, 0, sizeof(*process));
320         INIT_LIST_HEAD(&process->list);
321
322         process->pid = pid;
323         process->tid = tid;
324
325         if (!should_scan_process(opts, process))
326                 goto free;
327
328         snprintf(path, sizeof(path), "/proc/%d/task/%d/maps", pid, tid);
329         file = fopen(path, "rb");
330
331         if (!file)
332                 goto free;
333
334         maps = parse_maps(file, pid, tid);
335         fclose(file);
336         process->maps = maps;
337
338         snprintf(path, sizeof(path), "/proc/%d/task/%d/pagemap", pid, tid);
339         file = fopen(path, "rb");
340
341         if (!file)
342                 goto free;
343
344         parse_pageframe(file, pageframe, maps, opts);
345         fclose(file);
346
347         if (read_cmdline(pid, tid, process->name, sizeof(process->name)))
348                 goto free;
349
350         if (maps != NULL) {
351                 list_for_each_entry(maps, &process->maps->list, list) {
352                         process->pages_present += maps->pages_present;
353                         process->pages_swapped += maps->pages_swapped;
354                 }
355         }
356
357         list_add_tail(&process->list, &process_list->list);
358
359         return 1;
360 free:
361         free(process);
362
363         return 0;
364 }
365
366 static int parse_pid(DIR **dir)
367 {
368         struct dirent *dirent;
369         int error;
370
371 restart:
372         dirent = readdir(*dir);
373         if (!dirent) {
374                 if (errno == 0) {
375                         closedir(*dir);
376                         *dir = NULL;
377                         return 0;
378                 }
379                 error = errno;
380                 printf("Failed to read /proc directory: %s\n", strerror(error));
381                 return -1;
382         }
383
384         if (dirent->d_name[0] < '0' || dirent->d_name[0] > '9')
385                 goto restart;
386
387         return atoi(dirent->d_name);
388 }
389
390 static int opendir_check(DIR **dir, const char *path)
391 {
392         int error;
393
394         if (!*dir) {
395                 *dir = opendir(path);
396                 if (!dir) {
397                         error = errno;
398                         fprintf(stderr, "Failed to open %s directory: %s\n",
399                                 path, strerror(error));
400                         return -1;
401                 }
402         }
403
404         return 0;
405 }
406
407 static int get_next_tid(int pid, DIR **dir)
408 {
409         if (*dir == NULL) {
410                 char path[64];
411
412                 snprintf(path, sizeof(path), "/proc/%d/task/", pid);
413                 if (opendir_check(dir, path))
414                         return -1;
415         }
416
417         return parse_pid(dir);
418 }
419
420 static int get_next_pid(DIR **dir)
421 {
422         if (opendir_check(dir, "/proc"))
423                 return -1;
424
425         return parse_pid(dir);
426 }
427
428 static int get_next_pid_by_name(DIR **dir, char *name)
429 {
430         int pid;
431         char *pname;
432
433         if (opendir_check(dir, "/proc"))
434                 return -1;
435
436         while (1) {
437                 pid = parse_pid(dir);
438                 if (pid <= 0)
439                         break;
440
441                 pname = get_name_by_pid(pid);
442                 if (pname == NULL)
443                         continue;
444                 if (strcmp(pname, name))
445                         continue;
446
447                 return pid;
448         }
449
450         return 0;
451 }
452
453 static int read_pageframe_with_threads(int pid,
454                                 struct pageframe *pageframe,
455                                 struct process *process_list,
456                                 struct parse_opts *opts)
457 {
458         DIR *dir = NULL;
459         int tid;
460         int count = 0;
461
462         while (1) {
463                 if (opts->with_threads)
464                         tid = get_next_tid(pid, &dir);
465                 else
466                         tid = pid;
467
468                 if (tid <= 0)
469                         return count;
470
471                 count += read_pageframe(pid, tid, pageframe, process_list,
472                                         opts);
473
474                 if (!opts->with_threads)
475                         break;
476         }
477
478         return count;
479 }
480
481 int scan_all_pids(struct pageframe *pf, struct process *process_list,
482                 struct parse_opts *opts)
483 {
484         DIR *dir = NULL;
485         int pid;
486         int count = 0;
487
488         if (opts->parse_mask & PARSE_PROCESS_NAME) {
489                 while ((pid = get_next_pid_by_name(&dir, opts->name))) {
490                         count += read_pageframe_with_threads(pid, pf,
491                                                         process_list,
492                                                         opts);
493                 }
494                 dir = NULL;
495         }
496
497         if (opts->parse_mask & PARSE_PID)
498                 count = read_pageframe_with_threads(opts->pid, pf, process_list,
499                                                 opts);
500
501         if ((count == 0) && !(opts->parse_mask & PARSE_MAP_NAME)) {
502                 printf("Failed to find any matching processes "
503                         "with given arguments\n");
504                 return -1;
505         }
506
507         if (opts->parse_mask & PARSE_DUMP)
508                 return 0;
509
510         if (opts->parse_mask & PARSE_MAP_NAME) {
511                 while (1) {
512                         pid = get_next_pid(&dir);
513                         if (pid <= 0)
514                                 break;
515                         read_pageframe_with_threads(pid, pf, process_list,
516                                                 opts);
517                 }
518         }
519         /* Do not add new pages in the tree after the initial scan */
520         opts->parse_mask |= PARSE_NOADD_TREE;
521
522         while (1) {
523                 pid = get_next_pid(&dir);
524                 if (pid <= 0)
525                         break;
526                 read_pageframe_with_threads(pid, pf, process_list, opts);
527         }
528
529         return 0;
530 }