1 // Copyright 2019 The Chromium Authors
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/apple/scoped_mach_vm.h"
6
7 #include <mach/mach.h>
8
9 #include "base/memory/page_size.h"
10 #include "base/test/gtest_util.h"
11 #include "testing/gtest/include/gtest/gtest.h"
12
13 // Note: This test CANNOT be run multiple times within the same process (e.g.
14 // with --gtest_repeat). Allocating and deallocating in quick succession, even
15 // with different sizes, will typically result in the kernel returning the same
16 // address. If the allocation pattern is small->large->small, the second small
17 // allocation will report being part of the previously-deallocated large region.
18 // That will cause the GetRegionInfo() expectations to fail.
19
20 namespace base::apple {
21 namespace {
22
GetRegionInfo(vm_address_t * region_address,vm_size_t * region_size)23 void GetRegionInfo(vm_address_t* region_address, vm_size_t* region_size) {
24 vm_region_basic_info_64 region_info;
25 mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
26 mach_port_t object;
27 kern_return_t kr = vm_region_64(
28 mach_task_self(), region_address, region_size, VM_REGION_BASIC_INFO_64,
29 reinterpret_cast<vm_region_info_t>(®ion_info), &count, &object);
30 EXPECT_EQ(KERN_SUCCESS, kr);
31 }
32
TEST(ScopedMachVMTest,Basic)33 TEST(ScopedMachVMTest, Basic) {
34 vm_address_t address;
35 vm_size_t size = base::GetPageSize();
36 kern_return_t kr =
37 vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE);
38 ASSERT_EQ(KERN_SUCCESS, kr);
39
40 ScopedMachVM scoper(address, size);
41 EXPECT_EQ(address, scoper.address());
42 EXPECT_EQ(size, scoper.size());
43
44 // Test the initial region. In some cases on some platforms (macOS 13 on
45 // Intel, for example), Darwin may combine the requested allocation with
46 // an existing one. As a result, the allocated region may live in a
47 // larger region. Therefore, when we GetRegionInfo(), we want to check
48 // that our original region is a subset of (region_address, region_size)
49 // rather than being exactly equal to it.
50 vm_address_t region_address = address;
51 vm_size_t region_size;
52 GetRegionInfo(®ion_address, ®ion_size);
53 EXPECT_EQ(KERN_SUCCESS, kr);
54 EXPECT_GE(address, region_address);
55 EXPECT_LE(address + size, region_address + region_size);
56
57 {
58 ScopedMachVM scoper2;
59 EXPECT_EQ(0u, scoper2.address());
60 EXPECT_EQ(0u, scoper2.size());
61
62 scoper.swap(scoper2);
63
64 EXPECT_EQ(address, scoper2.address());
65 EXPECT_EQ(size, scoper2.size());
66
67 EXPECT_EQ(0u, scoper.address());
68 EXPECT_EQ(0u, scoper.size());
69 }
70
71 // After deallocation, the kernel will return the next highest address.
72 region_address = address;
73 GetRegionInfo(®ion_address, ®ion_size);
74 EXPECT_EQ(KERN_SUCCESS, kr);
75 EXPECT_LT(address, region_address);
76 }
77
TEST(ScopedMachVMTest,Reset)78 TEST(ScopedMachVMTest, Reset) {
79 vm_address_t address;
80 vm_size_t size = base::GetPageSize();
81 kern_return_t kr =
82 vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE);
83 ASSERT_EQ(KERN_SUCCESS, kr);
84
85 ScopedMachVM scoper(address, size);
86
87 // Test the initial region.
88 vm_address_t region_address = address;
89 vm_size_t region_size;
90 GetRegionInfo(®ion_address, ®ion_size);
91 EXPECT_EQ(KERN_SUCCESS, kr);
92 EXPECT_GE(address, region_address);
93 EXPECT_LE(address + size, region_address + region_size);
94
95 scoper.reset();
96
97 // After deallocation, the kernel will return the next highest address.
98 region_address = address;
99 GetRegionInfo(®ion_address, ®ion_size);
100 EXPECT_EQ(KERN_SUCCESS, kr);
101 EXPECT_LT(address, region_address);
102 }
103
TEST(ScopedMachVMTest,ResetSmallerAddress)104 TEST(ScopedMachVMTest, ResetSmallerAddress) {
105 vm_address_t address;
106 vm_size_t size = 2 * base::GetPageSize();
107 kern_return_t kr =
108 vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE);
109 ASSERT_EQ(KERN_SUCCESS, kr);
110
111 ScopedMachVM scoper(address, base::GetPageSize());
112
113 // Test the initial region.
114 vm_address_t region_address = address;
115 vm_size_t region_size;
116 GetRegionInfo(®ion_address, ®ion_size);
117 EXPECT_EQ(KERN_SUCCESS, kr);
118 EXPECT_EQ(address, region_address);
119 EXPECT_EQ(2u * base::GetPageSize(), region_size);
120
121 // This will free address..base::GetPageSize() that is currently in the
122 // scoper.
123 scoper.reset(address + base::GetPageSize(), base::GetPageSize());
124
125 // Verify that the region is now only one page.
126 region_address = address;
127 GetRegionInfo(®ion_address, ®ion_size);
128 EXPECT_EQ(address + base::GetPageSize(), region_address);
129 EXPECT_EQ(1u * base::GetPageSize(), region_size);
130 }
131
TEST(ScopedMachVMTest,ResetLargerAddressAndSize)132 TEST(ScopedMachVMTest, ResetLargerAddressAndSize) {
133 const vm_size_t kOnePage = base::GetPageSize();
134 const vm_size_t kTwoPages = 2 * kOnePage;
135 const vm_size_t kThreePages = 3 * kOnePage;
136
137 vm_address_t address;
138 kern_return_t kr =
139 vm_allocate(mach_task_self(), &address, kThreePages, VM_FLAGS_ANYWHERE);
140 ASSERT_EQ(KERN_SUCCESS, kr);
141
142 // Test the initial region.
143 vm_address_t region_address = address;
144 vm_size_t region_size;
145 GetRegionInfo(®ion_address, ®ion_size);
146 EXPECT_EQ(KERN_SUCCESS, kr);
147 EXPECT_GE(address, region_address);
148 EXPECT_LE(address + kThreePages, region_address + region_size);
149
150 ScopedMachVM scoper(address + kTwoPages, kOnePage);
151 // Expand the region to be larger.
152 scoper.reset(address, kThreePages);
153
154 // Verify that the region is still three pages.
155 region_address = address;
156 GetRegionInfo(®ion_address, ®ion_size);
157 EXPECT_GE(address, region_address);
158 EXPECT_LE(address + kThreePages, region_address + region_size);
159 }
160
TEST(ScopedMachVMTest,ResetLargerAddress)161 TEST(ScopedMachVMTest, ResetLargerAddress) {
162 const vm_size_t kThreePages = 3 * base::GetPageSize();
163 const vm_size_t kSixPages = 2 * kThreePages;
164
165 vm_address_t address;
166 kern_return_t kr =
167 vm_allocate(mach_task_self(), &address, kSixPages, VM_FLAGS_ANYWHERE);
168 ASSERT_EQ(KERN_SUCCESS, kr);
169
170 // Test the initial region.
171 vm_address_t region_address = address;
172 vm_size_t region_size;
173 GetRegionInfo(®ion_address, ®ion_size);
174 EXPECT_EQ(KERN_SUCCESS, kr);
175 EXPECT_GE(address, region_address);
176 EXPECT_LE(address + kSixPages, region_address + region_size);
177
178 ScopedMachVM scoper(address + kThreePages, kThreePages);
179
180 // Shift the region by three pages; the last three pages should be
181 // deallocated, while keeping the first three.
182 scoper.reset(address, kThreePages);
183
184 // Verify that the region is just three pages.
185 region_address = address;
186 GetRegionInfo(®ion_address, ®ion_size);
187 EXPECT_GE(address, region_address);
188 EXPECT_LE(address + kThreePages, region_address + region_size);
189 }
190
TEST(ScopedMachVMTest,ResetUnaligned)191 TEST(ScopedMachVMTest, ResetUnaligned) {
192 const vm_size_t kOnePage = base::GetPageSize();
193 const vm_size_t kTwoPages = 2 * kOnePage;
194
195 vm_address_t address;
196 kern_return_t kr =
197 vm_allocate(mach_task_self(), &address, kTwoPages, VM_FLAGS_ANYWHERE);
198 ASSERT_EQ(KERN_SUCCESS, kr);
199
200 ScopedMachVM scoper;
201
202 // Test the initial region.
203 vm_address_t region_address = address;
204 vm_size_t region_size;
205 GetRegionInfo(®ion_address, ®ion_size);
206 EXPECT_GE(address, region_address);
207 EXPECT_LE(address + kTwoPages, region_address + region_size);
208
209 // Initialize with unaligned size.
210 scoper.reset_unaligned(address + kOnePage, kOnePage - 3);
211 // Reset with another unaligned size.
212 scoper.reset_unaligned(address + kOnePage, kOnePage - 11);
213
214 // The entire unaligned page gets deallocated.
215 region_address = address;
216 GetRegionInfo(®ion_address, ®ion_size);
217 EXPECT_GE(address, region_address);
218 EXPECT_LE(address + kOnePage, region_address + region_size);
219
220 // Reset with the remaining page.
221 scoper.reset_unaligned(address, base::GetPageSize());
222 }
223
224 #if DCHECK_IS_ON()
225
TEST(ScopedMachVMTest,ResetMustBeAligned)226 TEST(ScopedMachVMTest, ResetMustBeAligned) {
227 const vm_size_t kOnePage = base::GetPageSize();
228 const vm_size_t kTwoPages = 2 * kOnePage;
229
230 vm_address_t address;
231 kern_return_t kr =
232 vm_allocate(mach_task_self(), &address, kTwoPages, VM_FLAGS_ANYWHERE);
233 ASSERT_EQ(KERN_SUCCESS, kr);
234
235 ScopedMachVM scoper;
236 EXPECT_DCHECK_DEATH(scoper.reset(address, kOnePage + 1));
237 }
238
239 #endif // DCHECK_IS_ON()
240
241 } // namespace
242 } // namespace base::apple
243