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.
5 #include "base/trace_event/process_memory_maps_dump_provider.h"
10 #include "base/logging.h"
11 #include "base/process/process_metrics.h"
12 #include "base/trace_event/process_memory_dump.h"
13 #include "base/trace_event/process_memory_maps.h"
16 namespace trace_event
{
18 #if defined(OS_LINUX) || defined(OS_ANDROID)
20 std::istream
* ProcessMemoryMapsDumpProvider::proc_smaps_for_testing
= nullptr;
24 const uint32 kMaxLineSize
= 4096;
26 bool ParseSmapsHeader(std::istream
* smaps
,
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.
31 std::string protection_flags
;
33 *smaps
>> std::hex
>> region
->start_address
;
35 *smaps
>> std::hex
>> end_addr
;
36 if (end_addr
> region
->start_address
) {
37 region
->size_in_bytes
= end_addr
- region
->start_address
;
39 // This is not just paranoia, it can actually happen (See crbug.com/461237).
40 region
->size_in_bytes
= 0;
44 region
->protection_flags
= 0;
45 *smaps
>> protection_flags
;
46 CHECK_EQ(4UL, protection_flags
.size());
47 if (protection_flags
[0] == 'r') {
48 region
->protection_flags
|=
49 ProcessMemoryMaps::VMRegion::kProtectionFlagsRead
;
51 if (protection_flags
[1] == 'w') {
52 region
->protection_flags
|=
53 ProcessMemoryMaps::VMRegion::kProtectionFlagsWrite
;
55 if (protection_flags
[2] == 'x') {
56 region
->protection_flags
|=
57 ProcessMemoryMaps::VMRegion::kProtectionFlagsExec
;
59 *smaps
>> ignored
; // Ignore mapped file offset.
60 *smaps
>> ignored
; // Ignore device maj-min (fc:01 in the example above).
61 *smaps
>> ignored
; // Ignore inode number (1234 in the example above).
63 while (smaps
->peek() == ' ')
65 char mapped_file
[kMaxLineSize
];
66 smaps
->getline(mapped_file
, sizeof(mapped_file
));
67 region
->mapped_file
= mapped_file
;
72 uint64
ReadCounterBytes(std::istream
* smaps
) {
73 uint64 counter_value
= 0;
74 *smaps
>> std::dec
>> counter_value
;
75 return counter_value
* 1024;
78 uint32
ParseSmapsCounter(std::istream
* smaps
,
79 ProcessMemoryMaps::VMRegion
* region
) {
80 // A smaps counter lines looks as follows: "RSS: 0 Kb\n"
82 std::string counter_name
;
83 *smaps
>> counter_name
;
85 // TODO(primiano): "Swap" should also be accounted as resident. Check
86 // whether Rss isn't already counting swapped and fix below if that is
88 if (counter_name
== "Pss:") {
89 region
->byte_stats_proportional_resident
= ReadCounterBytes(smaps
);
90 } else if (counter_name
== "Private_Dirty:") {
91 region
->byte_stats_private_dirty_resident
= ReadCounterBytes(smaps
);
92 } else if (counter_name
== "Private_Clean:") {
93 region
->byte_stats_private_clean_resident
= ReadCounterBytes(smaps
);
94 } else if (counter_name
== "Shared_Dirty:") {
95 region
->byte_stats_shared_dirty_resident
= ReadCounterBytes(smaps
);
96 } else if (counter_name
== "Shared_Clean:") {
97 region
->byte_stats_shared_clean_resident
= ReadCounterBytes(smaps
);
98 } else if (counter_name
== "Swap:") {
99 region
->byte_stats_swapped
= ReadCounterBytes(smaps
);
105 // Paranoid check against changes of the Kernel /proc interface.
109 DCHECK_EQ("kB", unit
);
113 smaps
->ignore(kMaxLineSize
, '\n');
118 uint32
ReadLinuxProcSmapsFile(std::istream
* smaps
, ProcessMemoryMaps
* pmm
) {
122 const uint32 kNumExpectedCountersPerRegion
= 6;
123 uint32 counters_parsed_for_current_region
= 0;
124 uint32 num_valid_regions
= 0;
125 ProcessMemoryMaps::VMRegion region
;
126 bool should_add_current_region
= false;
128 int next
= smaps
->peek();
129 if (next
== std::ifstream::traits_type::eof() || next
== '\n')
131 if (isxdigit(next
) && !isupper(next
)) {
132 region
= ProcessMemoryMaps::VMRegion();
133 counters_parsed_for_current_region
= 0;
134 should_add_current_region
= ParseSmapsHeader(smaps
, ®ion
);
136 counters_parsed_for_current_region
+= ParseSmapsCounter(smaps
, ®ion
);
137 DCHECK_LE(counters_parsed_for_current_region
,
138 kNumExpectedCountersPerRegion
);
139 if (counters_parsed_for_current_region
== kNumExpectedCountersPerRegion
) {
140 if (should_add_current_region
) {
141 pmm
->AddVMRegion(region
);
143 should_add_current_region
= false;
148 return num_valid_regions
;
152 #endif // defined(OS_LINUX) || defined(OS_ANDROID)
155 ProcessMemoryMapsDumpProvider
* ProcessMemoryMapsDumpProvider::GetInstance() {
156 return Singleton
<ProcessMemoryMapsDumpProvider
,
157 LeakySingletonTraits
<ProcessMemoryMapsDumpProvider
>>::get();
160 ProcessMemoryMapsDumpProvider::ProcessMemoryMapsDumpProvider() {
163 ProcessMemoryMapsDumpProvider::~ProcessMemoryMapsDumpProvider() {
166 // Called at trace dump point time. Creates a snapshot of the memory maps for
167 // the current process.
168 bool ProcessMemoryMapsDumpProvider::OnMemoryDump(const MemoryDumpArgs
& args
,
169 ProcessMemoryDump
* pmd
) {
170 // Snapshot of memory maps is not taken for light dump requests.
171 if (args
.level_of_detail
== MemoryDumpLevelOfDetail::LIGHT
)
176 #if defined(OS_LINUX) || defined(OS_ANDROID)
177 if (UNLIKELY(proc_smaps_for_testing
)) {
178 res
= ReadLinuxProcSmapsFile(proc_smaps_for_testing
, pmd
->process_mmaps());
180 std::ifstream
proc_self_smaps("/proc/self/smaps");
181 res
= ReadLinuxProcSmapsFile(&proc_self_smaps
, pmd
->process_mmaps());
184 LOG(ERROR
) << "ProcessMemoryMaps dump provider is supported only on Linux";
188 pmd
->set_has_process_mmaps();
195 } // namespace trace_event