1 /*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "perfetto/base/paged_memory.h"
18
19 #include <stdint.h>
20
21 #include "gtest/gtest.h"
22 #include "perfetto/base/build_config.h"
23 #include "src/base/test/vm_test_utils.h"
24
25 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX) && \
26 !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \
27 !PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
28 #include <sys/resource.h>
29 #endif
30
31 namespace perfetto {
32 namespace base {
33 namespace {
34
TEST(PagedMemoryTest,Basic)35 TEST(PagedMemoryTest, Basic) {
36 const size_t kNumPages = 10;
37 const size_t kSize = 4096 * kNumPages;
38 void* ptr_raw = nullptr;
39 {
40 PagedMemory mem = PagedMemory::Allocate(kSize);
41 ASSERT_TRUE(mem.IsValid());
42 ASSERT_EQ(0u, reinterpret_cast<uintptr_t>(mem.Get()) % 4096);
43 ptr_raw = mem.Get();
44 for (size_t i = 0; i < kSize / sizeof(uint64_t); i++)
45 ASSERT_EQ(0u, *(reinterpret_cast<uint64_t*>(mem.Get()) + i));
46
47 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
48 ASSERT_TRUE(vm_test_utils::IsMapped(ptr_raw, kSize));
49 #endif
50
51 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
52 PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
53 ASSERT_TRUE(mem.AdviseDontNeed(ptr_raw, kSize));
54
55 // Make sure the pages were removed from the working set.
56 ASSERT_FALSE(vm_test_utils::IsMapped(ptr_raw, kSize));
57 #endif
58 }
59
60 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
61 // Freed memory is necessarily not mapped in to the process.
62 ASSERT_FALSE(vm_test_utils::IsMapped(ptr_raw, kSize));
63 #endif
64 }
65
TEST(PagedMemoryTest,Uncommitted)66 TEST(PagedMemoryTest, Uncommitted) {
67 constexpr size_t kNumPages = 4096;
68 constexpr size_t kSize = 4096 * kNumPages;
69 char* ptr_raw = nullptr;
70 {
71 PagedMemory mem = PagedMemory::Allocate(kSize, PagedMemory::kDontCommit);
72 ASSERT_TRUE(mem.IsValid());
73 ptr_raw = reinterpret_cast<char*>(mem.Get());
74
75 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
76 // Windows only commits the first 1024 pages.
77 constexpr size_t kMappedSize = 4096 * 1024;
78
79 for (size_t i = 0; i < kMappedSize / sizeof(uint64_t); i++)
80 ASSERT_EQ(0u, *(reinterpret_cast<uint64_t*>(mem.Get()) + i));
81
82 ASSERT_TRUE(vm_test_utils::IsMapped(ptr_raw, kMappedSize));
83
84 // Next page shouldn't be mapped.
85 ASSERT_FALSE(vm_test_utils::IsMapped(ptr_raw + kMappedSize, 4096));
86 EXPECT_DEATH({ ptr_raw[kMappedSize] = 'x'; }, ".*");
87
88 // Commit the remaining pages.
89 mem.EnsureCommitted(kSize);
90
91 for (size_t i = kMappedSize / sizeof(uint64_t);
92 i < kSize / sizeof(uint64_t); i++) {
93 ASSERT_EQ(0u, *(reinterpret_cast<uint64_t*>(mem.Get()) + i));
94 }
95 #elif PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
96 // Fuchsia doesn't yet support paging. So this should be a no-op.
97 mem.EnsureCommitted(kSize);
98 for (size_t i = 0; i < kSize / sizeof(uint64_t); i++)
99 ASSERT_EQ(0u, *(reinterpret_cast<uint64_t*>(mem.Get()) + i));
100 #else
101 // Linux only maps on access.
102 ASSERT_FALSE(vm_test_utils::IsMapped(ptr_raw, kSize));
103
104 // This should not have any effect.
105 mem.EnsureCommitted(kSize);
106 ASSERT_FALSE(vm_test_utils::IsMapped(ptr_raw, kSize));
107
108 for (size_t i = 0; i < kSize / sizeof(uint64_t); i++)
109 ASSERT_EQ(0u, *(reinterpret_cast<uint64_t*>(mem.Get()) + i));
110 ASSERT_TRUE(vm_test_utils::IsMapped(ptr_raw, kSize));
111 #endif
112 }
113
114 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
115 // Freed memory is necessarily not mapped in to the process.
116 ASSERT_FALSE(vm_test_utils::IsMapped(ptr_raw, kSize));
117 #endif
118 }
119
120 #if defined(ADDRESS_SANITIZER)
TEST(PagedMemoryTest,AccessUncommittedMemoryTriggersASAN)121 TEST(PagedMemoryTest, AccessUncommittedMemoryTriggersASAN) {
122 EXPECT_DEATH(
123 {
124 constexpr size_t kNumPages = 4096;
125 constexpr size_t kSize = 4096 * kNumPages;
126 PagedMemory mem =
127 PagedMemory::Allocate(kSize, PagedMemory::kDontCommit);
128 ASSERT_TRUE(mem.IsValid());
129 char* ptr_raw = reinterpret_cast<char*>(mem.Get());
130 // Only the first 1024 pages are mapped.
131 constexpr size_t kMappedSize = 4096 * 1024;
132 ptr_raw[kMappedSize] = 'x';
133 abort();
134 },
135 "AddressSanitizer: .*");
136 }
137 #endif // ADDRESS_SANITIZER
138
TEST(PagedMemoryTest,GuardRegions)139 TEST(PagedMemoryTest, GuardRegions) {
140 const size_t kSize = 4096;
141 PagedMemory mem = PagedMemory::Allocate(kSize);
142 ASSERT_TRUE(mem.IsValid());
143 volatile char* raw = reinterpret_cast<char*>(mem.Get());
144 EXPECT_DEATH({ raw[-1] = 'x'; }, ".*");
145 EXPECT_DEATH({ raw[kSize] = 'x'; }, ".*");
146 }
147
148 // Disable this on:
149 // MacOS: because it doesn't seem to have an equivalent rlimit to bound mmap().
150 // Fuchsia: doesn't support rlimit.
151 // Sanitizers: they seem to try to shadow mmaped memory and fail due to OOMs.
152 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX) && \
153 !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \
154 !PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA) && !defined(ADDRESS_SANITIZER) && \
155 !defined(LEAK_SANITIZER) && !defined(THREAD_SANITIZER) && \
156 !defined(MEMORY_SANITIZER)
157 // Glibc headers hit this on RLIMIT_ macros.
158 #pragma GCC diagnostic push
159 #if defined(__clang__)
160 #pragma GCC diagnostic ignored "-Wdisabled-macro-expansion"
161 #endif
TEST(PagedMemoryTest,Unchecked)162 TEST(PagedMemoryTest, Unchecked) {
163 const size_t kMemLimit = 256 * 1024 * 1024l;
164 struct rlimit limit {
165 kMemLimit, kMemLimit
166 };
167 // ASSERT_EXIT here is to spawn the test in a sub-process and avoid
168 // propagating the setrlimit() to other test units in case of failure.
169 ASSERT_EXIT(
170 {
171 ASSERT_EQ(0, setrlimit(RLIMIT_AS, &limit));
172 auto mem = PagedMemory::Allocate(kMemLimit * 2, PagedMemory::kMayFail);
173 ASSERT_FALSE(mem.IsValid());
174 // Use _exit() instead of exit() to avoid calling destructors on child
175 // process death, which may interfere with the parent process's test
176 // launcher expectations.
177 _exit(0);
178 },
179 ::testing::ExitedWithCode(0), "");
180 }
181 #pragma GCC diagnostic pop
182 #endif
183
184 } // namespace
185 } // namespace base
186 } // namespace perfetto
187