1// Copyright 2015 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 "base/trace_event/process_memory_maps_dump_provider.h" 6 7#include <stdint.h> 8 9#include "base/files/scoped_file.h" 10#include "base/format_macros.h" 11#include "base/logging.h" 12#include "base/strings/string_util.h" 13#include "base/trace_event/process_memory_dump.h" 14#include "base/trace_event/process_memory_maps.h" 15 16namespace base { 17namespace trace_event { 18 19// static 20FILE* ProcessMemoryMapsDumpProvider::proc_smaps_for_testing = nullptr; 21 22namespace { 23 24const uint32_t kMaxLineSize = 4096; 25 26bool ParseSmapsHeader(const char* header_line, 27 ProcessMemoryMaps::VMRegion* region) { 28 // e.g., "00400000-00421000 r-xp 00000000 fc:01 1234 /foo.so\n" 29 bool res = true; // Whether this region should be appended or skipped. 30 uint64_t end_addr = 0; 31 char protection_flags[5] = {0}; 32 char mapped_file[kMaxLineSize]; 33 34 if (sscanf(header_line, "%" SCNx64 "-%" SCNx64 " %4c %*s %*s %*s%4095[^\n]\n", 35 ®ion->start_address, &end_addr, protection_flags, 36 mapped_file) != 4) 37 return false; 38 39 if (end_addr > region->start_address) { 40 region->size_in_bytes = end_addr - region->start_address; 41 } else { 42 // This is not just paranoia, it can actually happen (See crbug.com/461237). 43 region->size_in_bytes = 0; 44 res = false; 45 } 46 47 region->protection_flags = 0; 48 if (protection_flags[0] == 'r') { 49 region->protection_flags |= 50 ProcessMemoryMaps::VMRegion::kProtectionFlagsRead; 51 } 52 if (protection_flags[1] == 'w') { 53 region->protection_flags |= 54 ProcessMemoryMaps::VMRegion::kProtectionFlagsWrite; 55 } 56 if (protection_flags[2] == 'x') { 57 region->protection_flags |= 58 ProcessMemoryMaps::VMRegion::kProtectionFlagsExec; 59 } 60 61 region->mapped_file = mapped_file; 62 TrimWhitespaceASCII(region->mapped_file, TRIM_ALL, ®ion->mapped_file); 63 64 return res; 65} 66 67uint64_t ReadCounterBytes(char* counter_line) { 68 uint64_t counter_value = 0; 69 int res = sscanf(counter_line, "%*s %" SCNu64 " kB", &counter_value); 70 DCHECK_EQ(1, res); 71 return counter_value * 1024; 72} 73 74uint32_t ParseSmapsCounter(char* counter_line, 75 ProcessMemoryMaps::VMRegion* region) { 76 // A smaps counter lines looks as follows: "RSS: 0 Kb\n" 77 uint32_t res = 1; 78 char counter_name[20]; 79 int did_read = sscanf(counter_line, "%19[^\n ]", counter_name); 80 DCHECK_EQ(1, did_read); 81 82 if (strcmp(counter_name, "Pss:") == 0) { 83 region->byte_stats_proportional_resident = ReadCounterBytes(counter_line); 84 } else if (strcmp(counter_name, "Private_Dirty:") == 0) { 85 region->byte_stats_private_dirty_resident = ReadCounterBytes(counter_line); 86 } else if (strcmp(counter_name, "Private_Clean:") == 0) { 87 region->byte_stats_private_clean_resident = ReadCounterBytes(counter_line); 88 } else if (strcmp(counter_name, "Shared_Dirty:") == 0) { 89 region->byte_stats_shared_dirty_resident = ReadCounterBytes(counter_line); 90 } else if (strcmp(counter_name, "Shared_Clean:") == 0) { 91 region->byte_stats_shared_clean_resident = ReadCounterBytes(counter_line); 92 } else if (strcmp(counter_name, "Swap:") == 0) { 93 region->byte_stats_swapped = ReadCounterBytes(counter_line); 94 } else { 95 res = 0; 96 } 97 98 return res; 99} 100 101uint32_t ReadLinuxProcSmapsFile(FILE* smaps_file, ProcessMemoryMaps* pmm) { 102 if (!smaps_file) 103 return 0; 104 105 fseek(smaps_file, 0, SEEK_SET); 106 107 char line[kMaxLineSize]; 108 const uint32_t kNumExpectedCountersPerRegion = 6; 109 uint32_t counters_parsed_for_current_region = 0; 110 uint32_t num_valid_regions = 0; 111 ProcessMemoryMaps::VMRegion region; 112 bool should_add_current_region = false; 113 for (;;) { 114 line[0] = '\0'; 115 if (fgets(line, kMaxLineSize, smaps_file) == nullptr) 116 break; 117 DCHECK_GT(strlen(line), 0u); 118 if (isxdigit(line[0]) && !isupper(line[0])) { 119 region = ProcessMemoryMaps::VMRegion(); 120 counters_parsed_for_current_region = 0; 121 should_add_current_region = ParseSmapsHeader(line, ®ion); 122 } else { 123 counters_parsed_for_current_region += ParseSmapsCounter(line, ®ion); 124 DCHECK_LE(counters_parsed_for_current_region, 125 kNumExpectedCountersPerRegion); 126 if (counters_parsed_for_current_region == kNumExpectedCountersPerRegion) { 127 if (should_add_current_region) { 128 pmm->AddVMRegion(region); 129 ++num_valid_regions; 130 should_add_current_region = false; 131 } 132 } 133 } 134 } 135 return num_valid_regions; 136} 137 138} // namespace 139 140// static 141ProcessMemoryMapsDumpProvider* ProcessMemoryMapsDumpProvider::GetInstance() { 142 return Singleton<ProcessMemoryMapsDumpProvider, 143 LeakySingletonTraits<ProcessMemoryMapsDumpProvider>>::get(); 144} 145 146ProcessMemoryMapsDumpProvider::ProcessMemoryMapsDumpProvider() { 147} 148 149ProcessMemoryMapsDumpProvider::~ProcessMemoryMapsDumpProvider() { 150} 151 152// Called at trace dump point time. Creates a snapshot of the memory maps for 153// the current process. 154bool ProcessMemoryMapsDumpProvider::OnMemoryDump(const MemoryDumpArgs& args, 155 ProcessMemoryDump* pmd) { 156 // Snapshot of memory maps is not taken for light dump requests. 157 if (args.level_of_detail == MemoryDumpLevelOfDetail::LIGHT) 158 return true; 159 160 uint32_t res = 0; 161 if (UNLIKELY(proc_smaps_for_testing)) { 162 res = ReadLinuxProcSmapsFile(proc_smaps_for_testing, pmd->process_mmaps()); 163 } else { 164 ScopedFILE smaps_file(fopen("/proc/self/smaps", "r")); 165 res = ReadLinuxProcSmapsFile(smaps_file.get(), pmd->process_mmaps()); 166 } 167 168 if (res > 0) { 169 pmd->set_has_process_mmaps(); 170 return true; 171 } 172 return false; 173} 174 175} // namespace trace_event 176} // namespace base 177