Home | History | Annotate | Download | only in memdump
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include <fcntl.h>
      6 #include <signal.h>
      7 #include <sys/types.h>
      8 #include <unistd.h>
      9 
     10 #include <algorithm>
     11 #include <cstring>
     12 #include <fstream>
     13 #include <iostream>
     14 #include <limits>
     15 #include <string>
     16 #include <utility>
     17 #include <vector>
     18 
     19 #include "base/base64.h"
     20 #include "base/basictypes.h"
     21 #include "base/bind.h"
     22 #include "base/callback_helpers.h"
     23 #include "base/containers/hash_tables.h"
     24 #include "base/file_util.h"
     25 #include "base/files/scoped_file.h"
     26 #include "base/logging.h"
     27 #include "base/strings/string_number_conversions.h"
     28 #include "base/strings/string_split.h"
     29 #include "base/strings/stringprintf.h"
     30 
     31 const unsigned int kPageSize = getpagesize();
     32 
     33 namespace {
     34 
     35 class BitSet {
     36  public:
     37   void resize(size_t nbits) {
     38     data_.resize((nbits + 7) / 8);
     39   }
     40 
     41   void set(uint32 bit) {
     42     const uint32 byte_idx = bit / 8;
     43     CHECK(byte_idx < data_.size());
     44     data_[byte_idx] |= (1 << (bit & 7));
     45   }
     46 
     47   std::string AsB64String() const {
     48     std::string bits(&data_[0], data_.size());
     49     std::string b64_string;
     50     base::Base64Encode(bits, &b64_string);
     51     return b64_string;
     52   }
     53 
     54  private:
     55   std::vector<char> data_;
     56 };
     57 
     58 // An entry in /proc/<pid>/pagemap.
     59 struct PageMapEntry {
     60   uint64 page_frame_number : 55;
     61   uint unused : 8;
     62   uint present : 1;
     63 };
     64 
     65 // Describes a memory page.
     66 struct PageInfo {
     67   int64 page_frame_number; // Physical page id, also known as PFN.
     68   int64 flags;
     69   int32 times_mapped;
     70 };
     71 
     72 struct PageCount {
     73   PageCount() : total_count(0), unevictable_count(0) {}
     74 
     75   int total_count;
     76   int unevictable_count;
     77 };
     78 
     79 struct MemoryMap {
     80   std::string name;
     81   std::string flags;
     82   uint start_address;
     83   uint end_address;
     84   uint offset;
     85   PageCount private_pages;
     86   // app_shared_pages[i] contains the number of pages mapped in i+2 processes
     87   // (only among the processes that are being analyzed).
     88   std::vector<PageCount> app_shared_pages;
     89   PageCount other_shared_pages;
     90   std::vector<PageInfo> committed_pages;
     91   // committed_pages_bits is a bitset reflecting the present bit for all the
     92   // virtual pages of the mapping.
     93   BitSet committed_pages_bits;
     94 };
     95 
     96 struct ProcessMemory {
     97   pid_t pid;
     98   std::vector<MemoryMap> memory_maps;
     99 };
    100 
    101 bool PageIsUnevictable(const PageInfo& page_info) {
    102   // These constants are taken from kernel-page-flags.h.
    103   const int KPF_DIRTY = 4; // Note that only file-mapped pages can be DIRTY.
    104   const int KPF_ANON = 12; // Anonymous pages are dirty per definition.
    105   const int KPF_UNEVICTABLE = 18;
    106   const int KPF_MLOCKED = 33;
    107 
    108   return (page_info.flags & ((1ll << KPF_DIRTY) |
    109                              (1ll << KPF_ANON) |
    110                              (1ll << KPF_UNEVICTABLE) |
    111                              (1ll << KPF_MLOCKED))) ?
    112                              true : false;
    113 }
    114 
    115 // Number of times a physical page is mapped in a process.
    116 typedef base::hash_map<uint64, int> PFNMap;
    117 
    118 // Parses lines from /proc/<PID>/maps, e.g.:
    119 // 401e7000-401f5000 r-xp 00000000 103:02 158       /system/bin/linker
    120 bool ParseMemoryMapLine(const std::string& line,
    121                         std::vector<std::string>* tokens,
    122                         MemoryMap* memory_map) {
    123   tokens->clear();
    124   base::SplitString(line, ' ', tokens);
    125   if (tokens->size() < 2)
    126     return false;
    127   const std::string& addr_range = tokens->at(0);
    128   std::vector<std::string> range_tokens;
    129   base::SplitString(addr_range, '-', &range_tokens);
    130   uint64 tmp = 0;
    131   const std::string& start_address_token = range_tokens.at(0);
    132   if (!base::HexStringToUInt64(start_address_token, &tmp)) {
    133     return false;
    134   }
    135   memory_map->start_address = static_cast<uint>(tmp);
    136   const std::string& end_address_token = range_tokens.at(1);
    137   if (!base::HexStringToUInt64(end_address_token, &tmp)) {
    138     return false;
    139   }
    140   memory_map->end_address = static_cast<uint>(tmp);
    141   if (tokens->at(1).size() != strlen("rwxp"))
    142     return false;
    143   memory_map->flags.swap(tokens->at(1));
    144   if (!base::HexStringToUInt64(tokens->at(2), &tmp))
    145     return false;
    146   memory_map->offset = static_cast<uint>(tmp);
    147   memory_map->committed_pages_bits.resize(
    148       (memory_map->end_address - memory_map->start_address) / kPageSize);
    149   const int map_name_index = 5;
    150   if (tokens->size() >= map_name_index + 1) {
    151     for (std::vector<std::string>::const_iterator it =
    152              tokens->begin() + map_name_index; it != tokens->end(); ++it) {
    153       if (!it->empty()) {
    154         if (!memory_map->name.empty())
    155           memory_map->name.append(" ");
    156         memory_map->name.append(*it);
    157       }
    158     }
    159   }
    160   return true;
    161 }
    162 
    163 // Reads sizeof(T) bytes from file |fd| at |offset|.
    164 template <typename T>
    165 bool ReadFromFileAtOffset(int fd, off_t offset, T* value) {
    166   if (lseek64(fd, offset * sizeof(*value), SEEK_SET) < 0) {
    167     PLOG(ERROR) << "lseek";
    168     return false;
    169   }
    170   ssize_t bytes = read(fd, value, sizeof(*value));
    171   if (bytes != sizeof(*value) && bytes != 0) {
    172     PLOG(ERROR) << "read";
    173     return false;
    174   }
    175   return true;
    176 }
    177 
    178 // Fills |process_maps| in with the process memory maps identified by |pid|.
    179 bool GetProcessMaps(pid_t pid, std::vector<MemoryMap>* process_maps) {
    180   std::ifstream maps_file(base::StringPrintf("/proc/%d/maps", pid).c_str());
    181   if (!maps_file.good()) {
    182     PLOG(ERROR) << "open";
    183     return false;
    184   }
    185   std::string line;
    186   std::vector<std::string> tokens;
    187   while (std::getline(maps_file, line) && !line.empty()) {
    188     MemoryMap memory_map = {};
    189     if (!ParseMemoryMapLine(line, &tokens, &memory_map)) {
    190       LOG(ERROR) << "Could not parse line: " << line;
    191       return false;
    192     }
    193     process_maps->push_back(memory_map);
    194   }
    195   return true;
    196 }
    197 
    198 // Fills |committed_pages| in with the set of committed pages contained in the
    199 // provided memory map.
    200 bool GetPagesForMemoryMap(int pagemap_fd,
    201                           const MemoryMap& memory_map,
    202                           std::vector<PageInfo>* committed_pages,
    203                           BitSet* committed_pages_bits) {
    204   const off64_t offset = memory_map.start_address / kPageSize;
    205   if (lseek64(pagemap_fd, offset * sizeof(PageMapEntry), SEEK_SET) < 0) {
    206     PLOG(ERROR) << "lseek";
    207     return false;
    208   }
    209   for (uint addr = memory_map.start_address, page_index = 0;
    210        addr < memory_map.end_address;
    211        addr += kPageSize, ++page_index) {
    212     DCHECK_EQ(0, addr % kPageSize);
    213     PageMapEntry page_map_entry = {};
    214     COMPILE_ASSERT(sizeof(PageMapEntry) == sizeof(uint64), unexpected_size);
    215     ssize_t bytes = read(pagemap_fd, &page_map_entry, sizeof(page_map_entry));
    216     if (bytes != sizeof(PageMapEntry) && bytes != 0) {
    217       PLOG(ERROR) << "read";
    218       return false;
    219     }
    220     if (page_map_entry.present) {  // Ignore non-committed pages.
    221       if (page_map_entry.page_frame_number == 0)
    222         continue;
    223       PageInfo page_info = {};
    224       page_info.page_frame_number = page_map_entry.page_frame_number;
    225       committed_pages->push_back(page_info);
    226       committed_pages_bits->set(page_index);
    227     }
    228   }
    229   return true;
    230 }
    231 
    232 // Fills |committed_pages| with mapping count and flags information gathered
    233 // looking-up /proc/kpagecount and /proc/kpageflags.
    234 bool SetPagesInfo(int pagecount_fd,
    235                   int pageflags_fd,
    236                   std::vector<PageInfo>* pages) {
    237   for (std::vector<PageInfo>::iterator it = pages->begin();
    238        it != pages->end(); ++it) {
    239     PageInfo* const page_info = &*it;
    240     int64 times_mapped;
    241     if (!ReadFromFileAtOffset(
    242             pagecount_fd, page_info->page_frame_number, &times_mapped)) {
    243       return false;
    244     }
    245     DCHECK(times_mapped <= std::numeric_limits<int32_t>::max());
    246     page_info->times_mapped = static_cast<int32>(times_mapped);
    247 
    248     int64 page_flags;
    249     if (!ReadFromFileAtOffset(
    250             pageflags_fd, page_info->page_frame_number, &page_flags)) {
    251       return false;
    252     }
    253     page_info->flags = page_flags;
    254   }
    255   return true;
    256 }
    257 
    258 // Fills in the provided vector of Page Frame Number maps. This lets
    259 // ClassifyPages() know how many times each page is mapped in the processes.
    260 void FillPFNMaps(const std::vector<ProcessMemory>& processes_memory,
    261                  std::vector<PFNMap>* pfn_maps) {
    262   int current_process_index = 0;
    263   for (std::vector<ProcessMemory>::const_iterator it = processes_memory.begin();
    264        it != processes_memory.end(); ++it, ++current_process_index) {
    265     const std::vector<MemoryMap>& memory_maps = it->memory_maps;
    266     for (std::vector<MemoryMap>::const_iterator it = memory_maps.begin();
    267          it != memory_maps.end(); ++it) {
    268       const std::vector<PageInfo>& pages = it->committed_pages;
    269       for (std::vector<PageInfo>::const_iterator it = pages.begin();
    270            it != pages.end(); ++it) {
    271         const PageInfo& page_info = *it;
    272         PFNMap* const pfn_map = &(*pfn_maps)[current_process_index];
    273         const std::pair<PFNMap::iterator, bool> result = pfn_map->insert(
    274             std::make_pair(page_info.page_frame_number, 0));
    275         ++result.first->second;
    276       }
    277     }
    278   }
    279 }
    280 
    281 // Sets the private_count/app_shared_pages/other_shared_count fields of the
    282 // provided memory maps for each process.
    283 void ClassifyPages(std::vector<ProcessMemory>* processes_memory) {
    284   std::vector<PFNMap> pfn_maps(processes_memory->size());
    285   FillPFNMaps(*processes_memory, &pfn_maps);
    286   // Hash set keeping track of the physical pages mapped in a single process so
    287   // that they can be counted only once.
    288   base::hash_set<uint64> physical_pages_mapped_in_process;
    289 
    290   for (std::vector<ProcessMemory>::iterator it = processes_memory->begin();
    291        it != processes_memory->end(); ++it) {
    292     std::vector<MemoryMap>* const memory_maps = &it->memory_maps;
    293     physical_pages_mapped_in_process.clear();
    294     for (std::vector<MemoryMap>::iterator it = memory_maps->begin();
    295          it != memory_maps->end(); ++it) {
    296       MemoryMap* const memory_map = &*it;
    297       const size_t processes_count = processes_memory->size();
    298       memory_map->app_shared_pages.resize(processes_count - 1);
    299       const std::vector<PageInfo>& pages = memory_map->committed_pages;
    300       for (std::vector<PageInfo>::const_iterator it = pages.begin();
    301            it != pages.end(); ++it) {
    302         const PageInfo& page_info = *it;
    303         if (page_info.times_mapped == 1) {
    304           ++memory_map->private_pages.total_count;
    305           if (PageIsUnevictable(page_info))
    306             ++memory_map->private_pages.unevictable_count;
    307           continue;
    308         }
    309         const uint64 page_frame_number = page_info.page_frame_number;
    310         const std::pair<base::hash_set<uint64>::iterator, bool> result =
    311             physical_pages_mapped_in_process.insert(page_frame_number);
    312         const bool did_insert = result.second;
    313         if (!did_insert) {
    314           // This physical page (mapped multiple times in the same process) was
    315           // already counted.
    316           continue;
    317         }
    318         // See if the current physical page is also mapped in the other
    319         // processes that are being analyzed.
    320         int times_mapped = 0;
    321         int mapped_in_processes_count = 0;
    322         for (std::vector<PFNMap>::const_iterator it = pfn_maps.begin();
    323              it != pfn_maps.end(); ++it) {
    324           const PFNMap& pfn_map = *it;
    325           const PFNMap::const_iterator found_it = pfn_map.find(
    326               page_frame_number);
    327           if (found_it == pfn_map.end())
    328             continue;
    329           ++mapped_in_processes_count;
    330           times_mapped += found_it->second;
    331         }
    332         PageCount* page_count_to_update = NULL;
    333         if (times_mapped == page_info.times_mapped) {
    334           // The physical page is only mapped in the processes that are being
    335           // analyzed.
    336           if (mapped_in_processes_count > 1) {
    337             // The physical page is mapped in multiple processes.
    338             page_count_to_update =
    339                 &memory_map->app_shared_pages[mapped_in_processes_count - 2];
    340           } else {
    341             // The physical page is mapped multiple times in the same process.
    342             page_count_to_update = &memory_map->private_pages;
    343           }
    344         } else {
    345           page_count_to_update = &memory_map->other_shared_pages;
    346         }
    347         ++page_count_to_update->total_count;
    348         if (PageIsUnevictable(page_info))
    349           ++page_count_to_update->unevictable_count;
    350       }
    351     }
    352   }
    353 }
    354 
    355 void AppendAppSharedField(const std::vector<PageCount>& app_shared_pages,
    356                           std::string* out) {
    357   out->append("[");
    358   for (std::vector<PageCount>::const_iterator it = app_shared_pages.begin();
    359        it != app_shared_pages.end(); ++it) {
    360     out->append(base::IntToString(it->total_count * kPageSize));
    361     out->append(":");
    362     out->append(base::IntToString(it->unevictable_count * kPageSize));
    363     if (it + 1 != app_shared_pages.end())
    364       out->append(",");
    365   }
    366   out->append("]");
    367 }
    368 
    369 void DumpProcessesMemoryMapsInShortFormat(
    370     const std::vector<ProcessMemory>& processes_memory) {
    371   const int KB_PER_PAGE = kPageSize >> 10;
    372   std::vector<int> totals_app_shared(processes_memory.size());
    373   std::string buf;
    374   std::cout << "pid\tprivate\t\tshared_app\tshared_other (KB)\n";
    375   for (std::vector<ProcessMemory>::const_iterator it = processes_memory.begin();
    376        it != processes_memory.end(); ++it) {
    377     const ProcessMemory& process_memory = *it;
    378     std::fill(totals_app_shared.begin(), totals_app_shared.end(), 0);
    379     int total_private = 0, total_other_shared = 0;
    380     const std::vector<MemoryMap>& memory_maps = process_memory.memory_maps;
    381     for (std::vector<MemoryMap>::const_iterator it = memory_maps.begin();
    382          it != memory_maps.end(); ++it) {
    383       const MemoryMap& memory_map = *it;
    384       total_private += memory_map.private_pages.total_count;
    385       for (size_t i = 0; i < memory_map.app_shared_pages.size(); ++i)
    386         totals_app_shared[i] += memory_map.app_shared_pages[i].total_count;
    387       total_other_shared += memory_map.other_shared_pages.total_count;
    388     }
    389     double total_app_shared = 0;
    390     for (size_t i = 0; i < totals_app_shared.size(); ++i)
    391       total_app_shared += static_cast<double>(totals_app_shared[i]) / (i + 2);
    392     base::SStringPrintf(
    393         &buf, "%d\t%d\t\t%d\t\t%d\n",
    394         process_memory.pid,
    395         total_private * KB_PER_PAGE,
    396         static_cast<int>(total_app_shared) * KB_PER_PAGE,
    397         total_other_shared * KB_PER_PAGE);
    398     std::cout << buf;
    399   }
    400 }
    401 
    402 void DumpProcessesMemoryMapsInExtendedFormat(
    403     const std::vector<ProcessMemory>& processes_memory) {
    404   std::string buf;
    405   std::string app_shared_buf;
    406   for (std::vector<ProcessMemory>::const_iterator it = processes_memory.begin();
    407        it != processes_memory.end(); ++it) {
    408     const ProcessMemory& process_memory = *it;
    409     std::cout << "[ PID=" << process_memory.pid << "]" << '\n';
    410     const std::vector<MemoryMap>& memory_maps = process_memory.memory_maps;
    411     for (std::vector<MemoryMap>::const_iterator it = memory_maps.begin();
    412          it != memory_maps.end(); ++it) {
    413       const MemoryMap& memory_map = *it;
    414       app_shared_buf.clear();
    415       AppendAppSharedField(memory_map.app_shared_pages, &app_shared_buf);
    416       base::SStringPrintf(
    417           &buf,
    418           "%x-%x %s %x private_unevictable=%d private=%d shared_app=%s "
    419           "shared_other_unevictable=%d shared_other=%d \"%s\" [%s]\n",
    420           memory_map.start_address,
    421           memory_map.end_address,
    422           memory_map.flags.c_str(),
    423           memory_map.offset,
    424           memory_map.private_pages.unevictable_count * kPageSize,
    425           memory_map.private_pages.total_count * kPageSize,
    426           app_shared_buf.c_str(),
    427           memory_map.other_shared_pages.unevictable_count * kPageSize,
    428           memory_map.other_shared_pages.total_count * kPageSize,
    429           memory_map.name.c_str(),
    430           memory_map.committed_pages_bits.AsB64String().c_str());
    431       std::cout << buf;
    432     }
    433   }
    434 }
    435 
    436 bool CollectProcessMemoryInformation(int page_count_fd,
    437                                      int page_flags_fd,
    438                                      ProcessMemory* process_memory) {
    439   const pid_t pid = process_memory->pid;
    440   base::ScopedFD pagemap_fd(HANDLE_EINTR(open(
    441       base::StringPrintf("/proc/%d/pagemap", pid).c_str(), O_RDONLY)));
    442   if (!pagemap_fd.is_valid()) {
    443     PLOG(ERROR) << "open";
    444     return false;
    445   }
    446   std::vector<MemoryMap>* const process_maps = &process_memory->memory_maps;
    447   if (!GetProcessMaps(pid, process_maps))
    448     return false;
    449   for (std::vector<MemoryMap>::iterator it = process_maps->begin();
    450        it != process_maps->end(); ++it) {
    451     std::vector<PageInfo>* const committed_pages = &it->committed_pages;
    452     BitSet* const pages_bits = &it->committed_pages_bits;
    453     GetPagesForMemoryMap(pagemap_fd.get(), *it, committed_pages, pages_bits);
    454     SetPagesInfo(page_count_fd, page_flags_fd, committed_pages);
    455   }
    456   return true;
    457 }
    458 
    459 void KillAll(const std::vector<pid_t>& pids, int signal_number) {
    460   for (std::vector<pid_t>::const_iterator it = pids.begin(); it != pids.end();
    461        ++it) {
    462     kill(*it, signal_number);
    463   }
    464 }
    465 
    466 void ExitWithUsage() {
    467   LOG(ERROR) << "Usage: memdump [-a] <PID1>... <PIDN>";
    468   exit(EXIT_FAILURE);
    469 }
    470 
    471 }  // namespace
    472 
    473 int main(int argc, char** argv) {
    474   if (argc == 1)
    475     ExitWithUsage();
    476   const bool short_output = !strncmp(argv[1], "-a", 2);
    477   if (short_output) {
    478     if (argc == 2)
    479       ExitWithUsage();
    480     ++argv;
    481   }
    482   std::vector<pid_t> pids;
    483   for (const char* const* ptr = argv + 1; *ptr; ++ptr) {
    484     pid_t pid;
    485     if (!base::StringToInt(*ptr, &pid))
    486       return EXIT_FAILURE;
    487     pids.push_back(pid);
    488   }
    489 
    490   std::vector<ProcessMemory> processes_memory(pids.size());
    491   {
    492     base::ScopedFD page_count_fd(
    493         HANDLE_EINTR(open("/proc/kpagecount", O_RDONLY)));
    494     if (!page_count_fd.is_valid()) {
    495       PLOG(ERROR) << "open /proc/kpagecount";
    496       return EXIT_FAILURE;
    497     }
    498 
    499     base::ScopedFD page_flags_fd(open("/proc/kpageflags", O_RDONLY));
    500     if (!page_flags_fd.is_valid()) {
    501       PLOG(ERROR) << "open /proc/kpageflags";
    502       return EXIT_FAILURE;
    503     }
    504 
    505     base::ScopedClosureRunner auto_resume_processes(
    506         base::Bind(&KillAll, pids, SIGCONT));
    507     KillAll(pids, SIGSTOP);
    508     for (std::vector<pid_t>::const_iterator it = pids.begin(); it != pids.end();
    509          ++it) {
    510       ProcessMemory* const process_memory =
    511           &processes_memory[it - pids.begin()];
    512       process_memory->pid = *it;
    513       if (!CollectProcessMemoryInformation(
    514               page_count_fd.get(), page_flags_fd.get(), process_memory)) {
    515         return EXIT_FAILURE;
    516       }
    517     }
    518   }
    519 
    520   ClassifyPages(&processes_memory);
    521   if (short_output)
    522     DumpProcessesMemoryMapsInShortFormat(processes_memory);
    523   else
    524     DumpProcessesMemoryMapsInExtendedFormat(processes_memory);
    525   return EXIT_SUCCESS;
    526 }
    527