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, ×_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