• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
resize(size_t nbits)37   void resize(size_t nbits) {
38     data_.resize((nbits + 7) / 8);
39   }
40 
set(uint32 bit)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 
AsB64String() const47   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 {
PageCount__anon20b50a2c0111::PageCount73   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 
PageIsUnevictable(const PageInfo & page_info)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
ParseMemoryMapLine(const std::string & line,std::vector<std::string> * tokens,MemoryMap * memory_map)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>
ReadFromFileAtOffset(int fd,off_t offset,T * value)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|.
GetProcessMaps(pid_t pid,std::vector<MemoryMap> * process_maps)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.
GetPagesForMemoryMap(int pagemap_fd,const MemoryMap & memory_map,std::vector<PageInfo> * committed_pages,BitSet * committed_pages_bits)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.
SetPagesInfo(int pagecount_fd,int pageflags_fd,std::vector<PageInfo> * pages)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.
FillPFNMaps(const std::vector<ProcessMemory> & processes_memory,std::vector<PFNMap> * pfn_maps)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.
ClassifyPages(std::vector<ProcessMemory> * processes_memory)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 
AppendAppSharedField(const std::vector<PageCount> & app_shared_pages,std::string * out)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 
DumpProcessesMemoryMapsInShortFormat(const std::vector<ProcessMemory> & processes_memory)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 
DumpProcessesMemoryMapsInExtendedFormat(const std::vector<ProcessMemory> & processes_memory)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 
CollectProcessMemoryInformation(int page_count_fd,int page_flags_fd,ProcessMemory * process_memory)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 
KillAll(const std::vector<pid_t> & pids,int signal_number)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 
ExitWithUsage()466 void ExitWithUsage() {
467   LOG(ERROR) << "Usage: memdump [-a] <PID1>... <PIDN>";
468   exit(EXIT_FAILURE);
469 }
470 
471 }  // namespace
472 
main(int argc,char ** argv)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