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