ksminfo.c revision 266dde27811b78f466702295ba0173b8d8be5ada
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <errno.h>
18#include <stdbool.h>
19#include <stdlib.h>
20#include <sys/types.h>
21#include <unistd.h>
22#include <string.h>
23#include <fcntl.h>
24#include <stdint.h>
25#include <getopt.h>
26
27#include <pagemap/pagemap.h>
28
29#define MAX_FILENAME  64
30
31#define GROWTH_FACTOR 10
32
33#define NO_PATTERN    0x100
34
35static void usage(char *myname);
36static int getprocname(pid_t pid, char *buf, int len);
37static void print_ksm_pages(pm_map_t **maps, size_t num_maps, bool verbose);
38static bool is_pattern(uint8_t *data, size_t len);
39extern uint32_t hashword(const uint32_t *, size_t, int32_t);
40
41struct ksm_page {
42    uint32_t hash;
43    unsigned long *vaddr;
44    size_t vaddr_len, vaddr_size;
45    uint16_t pattern;
46};
47
48int main(int argc, char *argv[]) {
49    pm_kernel_t *ker;
50    pm_process_t *proc;
51    pid_t pid;
52    pm_map_t **maps;
53    size_t num_maps;
54    char cmdline[256]; // this must be within the range of int
55    int error;
56    int rc = EXIT_SUCCESS;
57    bool verbose = false;
58
59    opterr = 0;
60    do {
61        int c = getopt(argc, argv, "hv");
62        if (c == -1)
63            break;
64
65        switch (c) {
66            case 'v':
67                verbose = true;
68                break;
69            case 'h':
70                usage(argv[0]);
71                exit(EXIT_SUCCESS);
72            case '?':
73                fprintf(stderr, "unknown option: %c\n", optopt);
74                usage(argv[0]);
75                exit(EXIT_FAILURE);
76        }
77    } while (1);
78
79    if (optind != argc - 1) {
80        usage(argv[0]);
81        exit(EXIT_FAILURE);
82    }
83
84    pid = strtoul(argv[optind], NULL, 10);
85    if (pid == 0) {
86        fprintf(stderr, "Invalid PID\n");
87        exit(EXIT_FAILURE);
88    }
89
90    error = pm_kernel_create(&ker);
91    if (error) {
92        fprintf(stderr, "Error creating kernel interface -- "
93                        "does this kernel have pagemap?\n");
94        exit(EXIT_FAILURE);
95    }
96
97    error = pm_process_create(ker, pid, &proc);
98    if (error) {
99        fprintf(stderr, "warning: could not create process interface for %d\n", pid);
100        exit(EXIT_FAILURE);
101    }
102
103    error = pm_process_maps(proc, &maps, &num_maps);
104    if (error) {
105        fprintf(stderr, "warning: could not read process map for %d\n", pid);
106        rc = EXIT_FAILURE;
107        goto destroy_proc;
108    }
109
110    if (getprocname(pid, cmdline, sizeof(cmdline)) < 0) {
111        cmdline[0] = '\0';
112    }
113    printf("%s (%u):\n", cmdline, pid);
114    printf("Warning: this tool only compares the KSM CRCs of pages, there is a chance of "
115            "collisions\n");
116    print_ksm_pages(maps, num_maps, verbose);
117
118    free(maps);
119destroy_proc:
120    pm_process_destroy(proc);
121    return rc;
122}
123
124static void print_ksm_pages(pm_map_t **maps, size_t num_maps, bool verbose) {
125    size_t i, j, k;
126    size_t len;
127    uint64_t *pagemap;
128    size_t map_len;
129    uint64_t flags;
130    pm_kernel_t *ker;
131    int error;
132    unsigned long vaddr;
133    int fd;
134    off_t off;
135    char filename[MAX_FILENAME];
136    uint32_t *data;
137    uint32_t hash;
138    struct ksm_page *pages;
139    size_t pages_len, pages_size;
140
141    if (num_maps <= 0)
142        return;
143
144    ker = maps[0]->proc->ker;
145    error = snprintf(filename, MAX_FILENAME, "/proc/%d/mem", pm_process_pid(maps[0]->proc));
146    if (error < 0 || error >= MAX_FILENAME) {
147        return;
148    }
149
150    data = malloc(pm_kernel_pagesize(ker));
151    if (data == NULL) {
152        fprintf(stderr, "warning: not enough memory to malloc data buffer\n");
153        return;
154    }
155
156    fd = open(filename, O_RDONLY);
157    if (fd < 0) {
158        fprintf(stderr, "warning: could not open %s\n", filename);
159        goto err_open;
160    }
161
162    pages = NULL;
163    pages_size = 0;
164    pages_len = 0;
165
166    for (i = 0; i < num_maps; i++) {
167        error = pm_map_pagemap(maps[i], &pagemap, &map_len);
168        if (error) {
169            fprintf(stderr, "warning: could not read the pagemap of %d\n",
170                    pm_process_pid(maps[i]->proc));
171        }
172        for (j = 0; j < map_len; j++) {
173            error = pm_kernel_flags(ker, pagemap[j], &flags);
174            if (error) {
175                fprintf(stderr, "warning: could not read flags for pfn at address 0x%016llx\n",
176                        pagemap[i]);
177                continue;
178            }
179            if (!(flags & PM_PAGE_KSM)) {
180                continue;
181            }
182            vaddr = pm_map_start(maps[i]) + j * pm_kernel_pagesize(ker);
183            off = lseek(fd, vaddr, SEEK_SET);
184            if (off == (off_t)-1) {
185                fprintf(stderr, "warning: could not lseek to 0x%08lx\n", vaddr);
186                continue;
187            }
188            len = read(fd, data, pm_kernel_pagesize(ker));
189            if (len != pm_kernel_pagesize(ker)) {
190                fprintf(stderr, "warning: could not read page at 0x%08lx\n", vaddr);
191                continue;
192            }
193
194            hash = hashword(data, pm_kernel_pagesize(ker) / sizeof(*data), 17);
195
196            for (k = 0; k < pages_len; k++) {
197                if (pages[k].hash == hash) break;
198            }
199
200            if (k == pages_len) {
201                if (pages_len == pages_size) {
202                    struct ksm_page *tmp = realloc(pages,
203                            (pages_size + GROWTH_FACTOR) * sizeof(*pages));
204                    if (tmp == NULL) {
205                        fprintf(stderr, "warning: not enough memory to realloc pages struct\n");
206                        free(pagemap);
207                        goto err_realloc;
208                    }
209                    memset(&tmp[k], 0, sizeof(tmp[k]) * GROWTH_FACTOR);
210                    pages = tmp;
211                    pages_size += GROWTH_FACTOR;
212                }
213                pages[pages_len].hash = hash;
214                pages[pages_len].pattern = is_pattern((uint8_t *)data, pm_kernel_pagesize(ker)) ?
215                        (data[0] & 0xFF) : NO_PATTERN;
216                pages_len++;
217            }
218
219            if (verbose) {
220                if (pages[k].vaddr_len == pages[k].vaddr_size) {
221                    unsigned long *tmp = realloc(pages[k].vaddr,
222                            (pages[k].vaddr_size + GROWTH_FACTOR) * sizeof(*(pages[k].vaddr)));
223                    if (tmp == NULL) {
224                        fprintf(stderr, "warning: not enough memory to realloc vaddr array\n");
225                        free(pagemap);
226                        goto err_realloc;
227                    }
228                    memset(&tmp[pages[k].vaddr_len], 0, sizeof(tmp[pages[k].vaddr_len]) * GROWTH_FACTOR);
229                    pages[k].vaddr = tmp;
230                    pages[k].vaddr_size += GROWTH_FACTOR;
231                }
232                pages[k].vaddr[pages[k].vaddr_len] = vaddr;
233            }
234            pages[k].vaddr_len++;
235        }
236        free(pagemap);
237    }
238
239    for (i = 0; i < pages_len; i++) {
240        if (pages[i].pattern != NO_PATTERN) {
241            printf("0x%02x byte pattern: ", pages[i].pattern);
242        } else {
243            printf("KSM CRC 0x%08x:", pages[i].hash);
244        }
245        printf(" %4d page", pages[i].vaddr_len);
246        if (pages[i].vaddr_len > 1) {
247            printf("s");
248        }
249        printf("\n");
250
251        if (verbose) {
252            j = 0;
253            while (j < pages[i].vaddr_len) {
254                printf("                   ");
255                for (k = 0; k < 8 && j < pages[i].vaddr_len; k++, j++) {
256                    printf(" 0x%08lx", pages[i].vaddr[j]);
257                }
258                printf("\n");
259            }
260        }
261    }
262
263err_realloc:
264    if (verbose) {
265        for (i = 0; i < pages_len; i++) {
266            free(pages[i].vaddr);
267        }
268    }
269    free(pages);
270err_pages:
271    close(fd);
272err_open:
273    free(data);
274}
275
276static void usage(char *myname) {
277    fprintf(stderr, "Usage: %s [ -v | -h ] <pid>\n"
278                    "    -v  Verbose: print virtual addresses.\n"
279                    "    -h  Display this help screen.\n",
280    myname);
281}
282
283static bool is_pattern(uint8_t *data, size_t len) {
284    size_t i;
285    uint8_t first_byte = data[0];
286
287    for (i = 1; i < len; i++) {
288        if (first_byte != data[i]) return false;
289    }
290
291    return true;
292}
293
294/*
295 * Get the process name for a given PID. Inserts the process name into buffer
296 * buf of length len. The size of the buffer must be greater than zero to get
297 * any useful output.
298 *
299 * Note that fgets(3) only declares length as an int, so our buffer size is
300 * also declared as an int.
301 *
302 * Returns 0 on success, a positive value on partial success, and -1 on
303 * failure. Other interesting values:
304 *   1 on failure to create string to examine proc cmdline entry
305 *   2 on failure to open proc cmdline entry
306 *   3 on failure to read proc cmdline entry
307 */
308static int getprocname(pid_t pid, char *buf, int len) {
309    char *filename;
310    FILE *f;
311    int rc = 0;
312    static const char* unknown_cmdline = "<unknown>";
313
314    if (len <= 0) {
315        return -1;
316    }
317
318    if (asprintf(&filename, "/proc/%zd/cmdline", pid) < 0) {
319        rc = 1;
320        goto exit;
321    }
322
323    f = fopen(filename, "r");
324    if (f == NULL) {
325        rc = 2;
326        goto releasefilename;
327    }
328
329    if (fgets(buf, len, f) == NULL) {
330        rc = 3;
331        goto closefile;
332    }
333
334closefile:
335    (void) fclose(f);
336releasefilename:
337    free(filename);
338exit:
339    if (rc != 0) {
340        /*
341         * The process went away before we could read its process name. Try
342         * to give the user "<unknown>" here, but otherwise they get to look
343         * at a blank.
344         */
345        if (strlcpy(buf, unknown_cmdline, (size_t)len) >= (size_t)len) {
346            rc = 4;
347        }
348    }
349
350    return rc;
351}
352
353