1 // Copyright 2017 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 #include "services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics.h"
5
6 #include <set>
7 #include <vector>
8
9 #include "base/files/file_util.h"
10 #include "base/process/process_handle.h"
11 #include "base/process/process_metrics.h"
12 #include "build/build_config.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14
15 #if defined(OS_MAC)
16 #include <libgen.h>
17 #include <mach-o/dyld.h>
18 #endif
19
20 #if defined(OS_WIN)
21 #include <base/strings/sys_string_conversions.h>
22 #include <windows.h>
23 #endif
24
25 #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
26 #include <sys/mman.h>
27 #endif
28
29 namespace memory_instrumentation {
30
31 #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
32 namespace {
33 const char kTestSmaps1[] =
34 "00400000-004be000 r-xp 00000000 fc:01 1234 /file/1\n"
35 "Size: 760 kB\n"
36 "Rss: 296 kB\n"
37 "Pss: 162 kB\n"
38 "Shared_Clean: 228 kB\n"
39 "Shared_Dirty: 0 kB\n"
40 "Private_Clean: 0 kB\n"
41 "Private_Dirty: 68 kB\n"
42 "Referenced: 296 kB\n"
43 "Anonymous: 68 kB\n"
44 "AnonHugePages: 0 kB\n"
45 "Swap: 4 kB\n"
46 "KernelPageSize: 4 kB\n"
47 "MMUPageSize: 4 kB\n"
48 "Locked: 0 kB\n"
49 "VmFlags: rd ex mr mw me dw sd\n"
50 "ff000000-ff800000 -w-p 00001080 fc:01 0 /file/name with space\n"
51 "Size: 0 kB\n"
52 "Rss: 192 kB\n"
53 "Pss: 128 kB\n"
54 "Shared_Clean: 120 kB\n"
55 "Shared_Dirty: 4 kB\n"
56 "Private_Clean: 60 kB\n"
57 "Private_Dirty: 8 kB\n"
58 "Referenced: 296 kB\n"
59 "Anonymous: 0 kB\n"
60 "AnonHugePages: 0 kB\n"
61 "Swap: 0 kB\n"
62 "KernelPageSize: 4 kB\n"
63 "MMUPageSize: 4 kB\n"
64 "Locked: 1 kB\n"
65 "VmFlags: rd ex mr mw me dw sd";
66
67 const char kTestSmaps2[] =
68 // An invalid region, with zero size and overlapping with the last one
69 // (See crbug.com/461237).
70 "7fe7ce79c000-7fe7ce79c000 ---p 00000000 00:00 0 \n"
71 "Size: 4 kB\n"
72 "Rss: 0 kB\n"
73 "Pss: 0 kB\n"
74 "Shared_Clean: 0 kB\n"
75 "Shared_Dirty: 0 kB\n"
76 "Private_Clean: 0 kB\n"
77 "Private_Dirty: 0 kB\n"
78 "Referenced: 0 kB\n"
79 "Anonymous: 0 kB\n"
80 "AnonHugePages: 0 kB\n"
81 "Swap: 0 kB\n"
82 "KernelPageSize: 4 kB\n"
83 "MMUPageSize: 4 kB\n"
84 "Locked: 0 kB\n"
85 "VmFlags: rd ex mr mw me dw sd\n"
86 // A invalid region with its range going backwards.
87 "00400000-00200000 ---p 00000000 00:00 0 \n"
88 "Size: 4 kB\n"
89 "Rss: 0 kB\n"
90 "Pss: 0 kB\n"
91 "Shared_Clean: 0 kB\n"
92 "Shared_Dirty: 0 kB\n"
93 "Private_Clean: 0 kB\n"
94 "Private_Dirty: 0 kB\n"
95 "Referenced: 0 kB\n"
96 "Anonymous: 0 kB\n"
97 "AnonHugePages: 0 kB\n"
98 "Swap: 0 kB\n"
99 "KernelPageSize: 4 kB\n"
100 "MMUPageSize: 4 kB\n"
101 "Locked: 0 kB\n"
102 "VmFlags: rd ex mr mw me dw sd\n"
103 // A good anonymous region at the end.
104 "7fe7ce79c000-7fe7ce7a8000 ---p 00000000 00:00 0 \n"
105 "Size: 48 kB\n"
106 "Rss: 40 kB\n"
107 "Pss: 32 kB\n"
108 "Shared_Clean: 16 kB\n"
109 "Shared_Dirty: 12 kB\n"
110 "Private_Clean: 8 kB\n"
111 "Private_Dirty: 4 kB\n"
112 "Referenced: 40 kB\n"
113 "Anonymous: 16 kB\n"
114 "AnonHugePages: 0 kB\n"
115 "Swap: 0 kB\n"
116 "KernelPageSize: 4 kB\n"
117 "MMUPageSize: 4 kB\n"
118 "Locked: 11 kB\n"
119 "VmFlags: rd wr mr mw me ac sd\n";
120
CreateTempFileWithContents(const char * contents,base::ScopedFILE * file)121 void CreateTempFileWithContents(const char* contents, base::ScopedFILE* file) {
122 base::FilePath temp_path;
123 *file = CreateAndOpenTemporaryStream(&temp_path);
124 ASSERT_TRUE(*file);
125
126 ASSERT_TRUE(base::WriteFileDescriptor(fileno(file->get()), contents,
127 strlen(contents)));
128 }
129
130 } // namespace
131 #endif // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
132
TEST(OSMetricsTest,GivesNonZeroResults)133 TEST(OSMetricsTest, GivesNonZeroResults) {
134 base::ProcessId pid = base::kNullProcessId;
135 mojom::RawOSMemDump dump;
136 dump.platform_private_footprint = mojom::PlatformPrivateFootprint::New();
137 EXPECT_TRUE(OSMetrics::FillOSMemoryDump(pid, &dump));
138 EXPECT_TRUE(dump.platform_private_footprint);
139 #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) || \
140 defined(OS_FUCHSIA)
141 EXPECT_GT(dump.platform_private_footprint->rss_anon_bytes, 0u);
142 #elif defined(OS_WIN)
143 EXPECT_GT(dump.platform_private_footprint->private_bytes, 0u);
144 #elif defined(OS_MAC)
145 EXPECT_GT(dump.platform_private_footprint->internal_bytes, 0u);
146 #endif
147 }
148
149 #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
TEST(OSMetricsTest,ParseProcSmaps)150 TEST(OSMetricsTest, ParseProcSmaps) {
151 const uint32_t kProtR = mojom::VmRegion::kProtectionFlagsRead;
152 const uint32_t kProtW = mojom::VmRegion::kProtectionFlagsWrite;
153 const uint32_t kProtX = mojom::VmRegion::kProtectionFlagsExec;
154
155 // Emulate an empty /proc/self/smaps.
156 base::ScopedFILE empty_file(OpenFile(base::FilePath("/dev/null"), "r"));
157 ASSERT_TRUE(empty_file.get());
158 OSMetrics::SetProcSmapsForTesting(empty_file.get());
159 auto no_maps = OSMetrics::GetProcessMemoryMaps(base::kNullProcessId);
160 ASSERT_TRUE(no_maps.empty());
161
162 // Parse the 1st smaps file.
163 base::ScopedFILE temp_file1;
164 CreateTempFileWithContents(kTestSmaps1, &temp_file1);
165 OSMetrics::SetProcSmapsForTesting(temp_file1.get());
166 auto maps_1 = OSMetrics::GetProcessMemoryMaps(base::kNullProcessId);
167 ASSERT_EQ(2UL, maps_1.size());
168
169 EXPECT_EQ(0x00400000UL, maps_1[0]->start_address);
170 EXPECT_EQ(0x004be000UL - 0x00400000UL, maps_1[0]->size_in_bytes);
171 EXPECT_EQ(kProtR | kProtX, maps_1[0]->protection_flags);
172 EXPECT_EQ("/file/1", maps_1[0]->mapped_file);
173 EXPECT_EQ(162 * 1024UL, maps_1[0]->byte_stats_proportional_resident);
174 EXPECT_EQ(228 * 1024UL, maps_1[0]->byte_stats_shared_clean_resident);
175 EXPECT_EQ(0UL, maps_1[0]->byte_stats_shared_dirty_resident);
176 EXPECT_EQ(0UL, maps_1[0]->byte_stats_private_clean_resident);
177 EXPECT_EQ(68 * 1024UL, maps_1[0]->byte_stats_private_dirty_resident);
178 EXPECT_EQ(4 * 1024UL, maps_1[0]->byte_stats_swapped);
179 EXPECT_EQ(0 * 1024UL, maps_1[0]->byte_locked);
180
181 EXPECT_EQ(0xff000000UL, maps_1[1]->start_address);
182 EXPECT_EQ(0xff800000UL - 0xff000000UL, maps_1[1]->size_in_bytes);
183 EXPECT_EQ(kProtW, maps_1[1]->protection_flags);
184 EXPECT_EQ("/file/name with space", maps_1[1]->mapped_file);
185 EXPECT_EQ(128 * 1024UL, maps_1[1]->byte_stats_proportional_resident);
186 EXPECT_EQ(120 * 1024UL, maps_1[1]->byte_stats_shared_clean_resident);
187 EXPECT_EQ(4 * 1024UL, maps_1[1]->byte_stats_shared_dirty_resident);
188 EXPECT_EQ(60 * 1024UL, maps_1[1]->byte_stats_private_clean_resident);
189 EXPECT_EQ(8 * 1024UL, maps_1[1]->byte_stats_private_dirty_resident);
190 EXPECT_EQ(0 * 1024UL, maps_1[1]->byte_stats_swapped);
191 EXPECT_EQ(1 * 1024UL, maps_1[1]->byte_locked);
192
193 // Parse the 2nd smaps file.
194 base::ScopedFILE temp_file2;
195 CreateTempFileWithContents(kTestSmaps2, &temp_file2);
196 OSMetrics::SetProcSmapsForTesting(temp_file2.get());
197 auto maps_2 = OSMetrics::GetProcessMemoryMaps(base::kNullProcessId);
198 ASSERT_EQ(1UL, maps_2.size());
199 EXPECT_EQ(0x7fe7ce79c000UL, maps_2[0]->start_address);
200 EXPECT_EQ(0x7fe7ce7a8000UL - 0x7fe7ce79c000UL, maps_2[0]->size_in_bytes);
201 EXPECT_EQ(0U, maps_2[0]->protection_flags);
202 EXPECT_EQ("", maps_2[0]->mapped_file);
203 EXPECT_EQ(32 * 1024UL, maps_2[0]->byte_stats_proportional_resident);
204 EXPECT_EQ(16 * 1024UL, maps_2[0]->byte_stats_shared_clean_resident);
205 EXPECT_EQ(12 * 1024UL, maps_2[0]->byte_stats_shared_dirty_resident);
206 EXPECT_EQ(8 * 1024UL, maps_2[0]->byte_stats_private_clean_resident);
207 EXPECT_EQ(4 * 1024UL, maps_2[0]->byte_stats_private_dirty_resident);
208 EXPECT_EQ(0 * 1024UL, maps_2[0]->byte_stats_swapped);
209 EXPECT_EQ(11 * 1024UL, maps_2[0]->byte_locked);
210 }
211
TEST(OSMetricsTest,GetMappedAndResidentPages)212 TEST(OSMetricsTest, GetMappedAndResidentPages) {
213 const size_t kPages = 16;
214 const size_t kPageSize = base::GetPageSize();
215 const size_t kLength = kPages * kPageSize;
216
217 // mmap guarantees addr is aligned with kPagesize.
218 void* addr = mmap(NULL, kLength, PROT_READ | PROT_WRITE,
219 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
220
221 ASSERT_NE(MAP_FAILED, addr) << "mmap() failed";
222
223 std::set<size_t> pages;
224 uint8_t* array = static_cast<uint8_t*>(addr);
225 for (unsigned int i = 0; i < kPages / 2; ++i) {
226 int page = rand() % kPages;
227 int offset = rand() % kPageSize;
228 *static_cast<volatile uint8_t*>(array + page * kPageSize + offset) =
229 rand() % 256;
230 pages.insert(page);
231 }
232
233 size_t start_address = reinterpret_cast<size_t>(addr);
234
235 std::vector<uint8_t> accessed_pages_bitmap;
236 OSMetrics::MappedAndResidentPagesDumpState state =
237 OSMetrics::GetMappedAndResidentPages(
238 start_address, start_address + kLength, &accessed_pages_bitmap);
239
240 ASSERT_EQ(munmap(addr, kLength), 0);
241 if (state == OSMetrics::MappedAndResidentPagesDumpState::kAccessPagemapDenied)
242 return;
243
244 EXPECT_EQ(state == OSMetrics::MappedAndResidentPagesDumpState::kSuccess,
245 true);
246 std::set<size_t> accessed_pages_set;
247 for (size_t i = 0; i < accessed_pages_bitmap.size(); i++) {
248 for (int j = 0; j < 8; j++)
249 if (accessed_pages_bitmap[i] & (1 << j))
250 accessed_pages_set.insert(i * 8 + j);
251 }
252
253 EXPECT_EQ(pages == accessed_pages_set, true);
254 }
255
256 #endif // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
257
258 #if defined(OS_WIN)
DummyFunction()259 void DummyFunction() {}
260
TEST(OSMetricsTest,TestWinModuleReading)261 TEST(OSMetricsTest, TestWinModuleReading) {
262 auto maps = OSMetrics::GetProcessMemoryMaps(base::kNullProcessId);
263
264 wchar_t module_name[MAX_PATH];
265 DWORD result = GetModuleFileName(nullptr, module_name, MAX_PATH);
266 ASSERT_TRUE(result);
267 std::string executable_name = base::SysWideToNativeMB(module_name);
268
269 HMODULE module_containing_dummy = nullptr;
270 uintptr_t dummy_function_address =
271 reinterpret_cast<uintptr_t>(&DummyFunction);
272 result = GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
273 reinterpret_cast<LPCWSTR>(dummy_function_address),
274 &module_containing_dummy);
275 ASSERT_TRUE(result);
276 result = GetModuleFileName(nullptr, module_name, MAX_PATH);
277 ASSERT_TRUE(result);
278 std::string module_containing_dummy_name =
279 base::SysWideToNativeMB(module_name);
280
281 bool found_executable = false;
282 bool found_region_with_dummy = false;
283 for (const mojom::VmRegionPtr& region : maps) {
284 // We add a region just for byte_stats_proportional_resident which
285 // is empty other than that one stat.
286 if (region->byte_stats_proportional_resident > 0) {
287 EXPECT_EQ(0u, region->start_address);
288 EXPECT_EQ(0u, region->size_in_bytes);
289 continue;
290 }
291 EXPECT_NE(0u, region->start_address);
292 EXPECT_NE(0u, region->size_in_bytes);
293
294 if (region->mapped_file.find(executable_name) != std::string::npos)
295 found_executable = true;
296
297 if (dummy_function_address >= region->start_address &&
298 dummy_function_address <
299 region->start_address + region->size_in_bytes) {
300 found_region_with_dummy = true;
301 EXPECT_EQ(module_containing_dummy_name, region->mapped_file);
302 }
303 }
304 EXPECT_TRUE(found_executable);
305 EXPECT_TRUE(found_region_with_dummy);
306 }
307 #endif // defined(OS_WIN)
308
309 #if defined(OS_MAC)
310 namespace {
311
CheckMachORegions(const std::vector<mojom::VmRegionPtr> & maps)312 void CheckMachORegions(const std::vector<mojom::VmRegionPtr>& maps) {
313 uint32_t size = 100;
314 char full_path[size];
315 int result = _NSGetExecutablePath(full_path, &size);
316 ASSERT_EQ(0, result);
317 std::string name = basename(full_path);
318
319 bool found_appkit = false;
320 bool found_components_unittests = false;
321 for (const mojom::VmRegionPtr& region : maps) {
322 EXPECT_NE(0u, region->start_address);
323 EXPECT_NE(0u, region->size_in_bytes);
324
325 EXPECT_LT(region->size_in_bytes, 1ull << 32);
326 uint32_t required_protection_flags = mojom::VmRegion::kProtectionFlagsRead |
327 mojom::VmRegion::kProtectionFlagsExec;
328 if (region->mapped_file.find(name) != std::string::npos &&
329 region->protection_flags == required_protection_flags) {
330 found_components_unittests = true;
331 }
332
333 if (region->mapped_file.find("AppKit") != std::string::npos) {
334 found_appkit = true;
335 }
336 }
337 EXPECT_TRUE(found_components_unittests);
338 EXPECT_TRUE(found_appkit);
339 }
340
341 } // namespace
342
343 // Test failing on Mac ASan 64: https://crbug.com/852690
TEST(OSMetricsTest,DISABLED_TestMachOReading)344 TEST(OSMetricsTest, DISABLED_TestMachOReading) {
345 auto maps = OSMetrics::GetProcessMemoryMaps(base::kNullProcessId);
346 CheckMachORegions(maps);
347 maps = OSMetrics::GetProcessModules(base::kNullProcessId);
348 CheckMachORegions(maps);
349 }
350 #endif // defined(OS_MAC)
351
352 } // namespace memory_instrumentation
353