page-collect.c
1 /* page-collect.c -- collect a snapshot each of of the /proc/pid/maps files, 2 * with each VM region interleaved with a list of physical addresses 3 * which make up the virtual region. 4 * Copyright C2009 by EQware Engineering, Inc. 5 * 6 * page-collect.c is part of PageMapTools. 7 * 8 * PageMapTools is free software: you can redistribute it and/or modify 9 * it under the terms of version 3 of the GNU General Public License 10 * as published by the Free Software Foundation 11 * 12 * PageMapTools is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with PageMapTools. If not, see http://www.gnu.org/licenses. 19 */ 20 #define _LARGEFILE64_SOURCE 21 22 #include <stddef.h> 23 #include <stdlib.h> 24 #include <stdio.h> 25 #include <assert.h> 26 #include <errno.h> 27 #include <string.h> 28 29 #include <sys/types.h> 30 #include <dirent.h> 31 #include <sys/stat.h> 32 #include <unistd.h> 33 #include <fcntl.h> 34 35 #define FILENAMELEN 256 36 #define LINELEN 256 37 #define PAGE_SIZE 4096 38 39 #define PROC_DIR_NAME "/proc" 40 #define MAPS_NAME "maps" 41 #define PAGEMAP_NAME "pagemap" 42 #define CMDLINE_NAME "cmdline" 43 #define STAT_NAME "stat" 44 #define OUT_NAME "./page-collect.dat" 45 46 typedef int bool; 47 #define FALSE 0 48 #define TRUE (!FALSE) 49 50 51 /* ERR() -- 52 */ 53 #define ERR(format, ...) fprintf(stderr, format, ## __VA_ARGS__) 54 55 56 /* is_switch() -- 57 */ 58 static inline bool is_switch(char c) 59 { return c == '-'; 60 } 61 62 63 /* is_directory() -- 64 */ 65 static bool is_directory(const char *dirname) 66 { 67 struct stat buf; 68 int n; 69 70 assert(dirname != NULL); 71 72 n = stat(dirname, &buf); 73 74 return (n == 0 && (buf.st_mode & S_IFDIR) != 0)? TRUE: FALSE; 75 76 } /* is_directory */ 77 78 79 /* is_wholly_numeric() -- 80 */ 81 static bool is_wholly_numeric(const char *str) 82 { 83 assert(str != NULL); 84 85 while (*str != '0') 86 { 87 if (!isdigit(*str)) 88 { 89 return FALSE; 90 } 91 str++; 92 } 93 return TRUE; 94 95 } /* is_wholly_numeric */ 96 97 98 /* main() -- 99 */ 100 int main(int argc, char *argv[]) 101 { 102 struct dirent *de; 103 int n; 104 105 int pm = -1; 106 FILE *m = NULL; 107 FILE *out = NULL; 108 DIR *proc = NULL; 109 int retval = 0; 110 111 const char *out_name = OUT_NAME; 112 113 /* Process command-line arguments. 114 */ 115 for (n = 1; n < argc; n++) 116 { 117 char *arg = argv[n]; 118 119 if (is_switch(*arg)) 120 { 121 arg++; 122 123 /* -o out-file 124 */ 125 if (strncmp(arg, "o", 1) == 0) 126 { 127 if (arg[1] == '0') arg = argv[++n]; 128 else arg++; 129 130 out_name = arg; 131 } 132 133 /* Unknown switch. 134 */ 135 else goto usage; 136 } 137 else 138 { 139 usage: 140 fprintf(stderr, 141 "n" 142 "page-collect -- collect a snapshot each of of the /proc/pid/mapsn" 143 " files, with each VM region interleaved with a list of physicaln" 144 " addresses which make up the virtual region.n" 145 "n" 146 "usage: page-collect {switches}n" 147 "switches:n" 148 " -o out-file -- Output file name (def=%s)n" 149 "n", 150 OUT_NAME); 151 goto done; 152 } 153 } 154 155 /* Open output file for writing. 156 */ 157 out = fopen(out_name, "w"); 158 if (out == NULL) 159 { 160 ERR("Unable to open file "%s" for writing (errno=%d). (1)n", out_name, errno); 161 retval = -1; 162 goto done; 163 } 164 165 /* Open /proc directory for traversal. 166 */ 167 proc = opendir(PROC_DIR_NAME); 168 if (proc == NULL) 169 { 170 ERR("Unable to open directory "%s" for traversal (errno=%d). (4)n", PROC_DIR_NAME, errno); 171 retval = -1; 172 goto done; 173 } 174 175 /* For each entry in the /proc directory... 176 */ 177 goto enter; 178 while (de != NULL) 179 { 180 char d_name[FILENAMELEN]; 181 sprintf(d_name, "%s/%s", PROC_DIR_NAME, de->d_name); 182 183 /* ...if the entry is a numerically-named directory... 184 */ 185 if (is_directory(d_name) 186 && is_wholly_numeric(de->d_name)) 187 { 188 char m_name[FILENAMELEN]; 189 char pm_name[FILENAMELEN]; 190 char cl_name[FILENAMELEN]; 191 char p_name[LINELEN]; 192 char line[LINELEN]; 193 194 FILE *cl = NULL; 195 196 /* Open pid/maps file for reading. 197 */ 198 sprintf(m_name, "%s/%s/%s", PROC_DIR_NAME, de->d_name, MAPS_NAME); 199 m = fopen(m_name, "r"); 200 if (m == NULL) 201 { 202 ERR("Unable to open "%s" for reading (errno=%d) (5).n", m_name, errno); 203 continue; 204 } 205 206 /* Open pid/pagemap file for reading. 207 */ 208 sprintf(pm_name, "%s/%s/%s", PROC_DIR_NAME, de->d_name, PAGEMAP_NAME); 209 pm = open(pm_name, O_RDONLY); 210 if (pm == -1) 211 { 212 ERR("Unable to open "%s" for reading (errno=%d). (7)n", pm_name, errno); 213 continue; 214 } 215 216 /* Get process command-line or name string. 217 */ 218 p_name[0] = '0'; 219 220 /* Open pid/cmdline file for reading. Try for command-line. 221 */ 222 sprintf(cl_name, "%s/%s/%s", PROC_DIR_NAME, de->d_name, CMDLINE_NAME); 223 cl = fopen(cl_name, "r"); 224 if (cl == NULL) 225 { 226 ERR("Unable to open "%s" for reading (errno=%d). (7.1)n", cl_name, errno); 227 } 228 fgets(p_name, LINELEN, cl); 229 fclose(cl); 230 231 /* If no command-line was available, get the second field of the first 232 * line of the pid/stat file. 233 */ 234 if (strlen(p_name) == 0) 235 { 236 FILE *st; 237 char st_name[FILENAMELEN]; 238 char stat[LINELEN]; 239 char *s; 240 int i; 241 242 /* Open pid/stat file for reading. 243 */ 244 sprintf(st_name, "%s/%s/%s", PROC_DIR_NAME, de->d_name, STAT_NAME); 245 st = fopen(st_name, "r"); 246 if (st == NULL) 247 { 248 ERR("Unable to open "%s" for reading (errno=%d). (7.2)n", st_name, errno); 249 } 250 fgets(stat, LINELEN, st); 251 fclose(st); 252 253 s = stat; 254 i = 0; 255 while (*s != '0' && !isspace(*s)) s++; 256 while (*s != '0' && isspace(*s)) s++; 257 while (*s != '0' && !isspace(*s)) p_name[i++] = *s++; 258 p_name[i] = '0'; 259 } 260 261 /* For each maps file, output the filename and process info (ps entry). 262 */ 263 fprintf(out, "@ %s - %sn", m_name, p_name); 264 265 /* For each line in the maps file... 266 */ 267 while (fgets(line, LINELEN, m) != NULL) 268 { 269 unsigned long vm_start; 270 unsigned long vm_end; 271 int num_pages; 272 273 /* ...output the line... 274 */ 275 fprintf(out, "= %s", line); 276 277 /* ...then evaluate the range of virtual 278 * addresses it asserts. 279 */ 280 n = sscanf(line, "%lX-%lX", &vm_start, &vm_end); 281 if (n != 2) 282 { 283 ERR("Invalid line read from "%s": %s (6)n", m_name, line); 284 continue; 285 } 286 287 /* If the virtual address range is greater than 0... 288 */ 289 num_pages = (vm_end - vm_start) / PAGE_SIZE; 290 if (num_pages > 0) 291 { 292 long index = (vm_start / PAGE_SIZE) * sizeof(unsigned long long); 293 off64_t o; 294 ssize_t t; 295 296 /* Seek to appropriate index of pagemap file. 297 */ 298 o = lseek64(pm, index, SEEK_SET); 299 if (o != index) 300 { 301 ERR("Error seeking to %ld in file "%s" (errno=%d). (8)n", index, pm_name, errno); 302 continue; 303 } 304 305 /* For each page in the virtual address range... 306 */ 307 while (num_pages > 0) 308 { 309 unsigned long long pa; 310 311 /* Read a 64-bit word from each of the pagemap file... 312 */ 313 t = read(pm, &pa, sizeof(unsigned long long)); 314 if (t < 0) 315 { 316 ERR("Error reading file "%s" (errno=%d). (11)n", pm_name, errno); 317 goto do_continue; 318 } 319 320 /* ...and write the word to a single line of output. 321 */ 322 fprintf(out, ": %016llXn", pa); 323 324 num_pages--; 325 } 326 } 327 do_continue: 328 ; 329 } 330 if (pm != -1) 331 { 332 close(pm); 333 pm = -1; 334 } 335 } 336 if (m != NULL) 337 { 338 fclose(m); 339 m = NULL; 340 } 341 342 enter: 343 de = readdir(proc); 344 } 345 346 done: 347 if (proc != NULL) 348 { 349 closedir(proc); 350 } 351 if (pm != -1) 352 { 353 close(pm); 354 } 355 if (m != NULL) 356 { 357 fclose(m); 358 } 359 if (out != NULL) 360 { 361 fclose(out); 362 } 363 return retval; 364 365 } /* main */ 366