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