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