1*4f1223e8SApple OSS Distributions #include <sys/types.h>
2*4f1223e8SApple OSS Distributions #include <sys/sysctl.h>
3*4f1223e8SApple OSS Distributions #include <mach/mach.h>
4*4f1223e8SApple OSS Distributions #include <mach/mach_vm.h>
5*4f1223e8SApple OSS Distributions #include <mach/vm_reclaim.h>
6*4f1223e8SApple OSS Distributions #include <mach-o/dyld.h>
7*4f1223e8SApple OSS Distributions #include <os/atomic_private.h>
8*4f1223e8SApple OSS Distributions #include <signal.h>
9*4f1223e8SApple OSS Distributions #include <spawn.h>
10*4f1223e8SApple OSS Distributions #include <spawn_private.h>
11*4f1223e8SApple OSS Distributions #include <unistd.h>
12*4f1223e8SApple OSS Distributions
13*4f1223e8SApple OSS Distributions #include <darwintest.h>
14*4f1223e8SApple OSS Distributions #include <darwintest_multiprocess.h>
15*4f1223e8SApple OSS Distributions #include <darwintest_utils.h>
16*4f1223e8SApple OSS Distributions
17*4f1223e8SApple OSS Distributions #include <Kernel/kern/ledger.h>
18*4f1223e8SApple OSS Distributions extern int ledger(int cmd, caddr_t arg1, caddr_t arg2, caddr_t arg3);
19*4f1223e8SApple OSS Distributions
20*4f1223e8SApple OSS Distributions #include "memorystatus_assertion_helpers.h"
21*4f1223e8SApple OSS Distributions
22*4f1223e8SApple OSS Distributions // Some of the unit tests test deferred deallocations.
23*4f1223e8SApple OSS Distributions // For these we need to set a sufficiently large reclaim threshold
24*4f1223e8SApple OSS Distributions // to ensure their buffers aren't freed prematurely.
25*4f1223e8SApple OSS Distributions #define VM_RECLAIM_THRESHOLD_BOOTARG_HIGH "vm_reclaim_max_threshold=268435456"
26*4f1223e8SApple OSS Distributions #define VM_RECLAIM_THRESHOLD_BOOTARG_LOW "vm_reclaim_max_threshold=16384"
27*4f1223e8SApple OSS Distributions #define VM_RECLAIM_BOOTARG_DISABLED "vm_reclaim_max_threshold=0"
28*4f1223e8SApple OSS Distributions #define VM_RECLAIM_THRESHOLD_SYSCTL_HIGH "vm.reclaim_max_threshold=268435456"
29*4f1223e8SApple OSS Distributions #define VM_RECLAIM_THRESHOLD_SYSCTL_LOW "vm.reclaim_max_threshold=16384"
30*4f1223e8SApple OSS Distributions #define VM_RECLAIM_SYSCTL_DISABLED "vm.reclaim_max_threshold=0"
31*4f1223e8SApple OSS Distributions
32*4f1223e8SApple OSS Distributions T_GLOBAL_META(
33*4f1223e8SApple OSS Distributions T_META_NAMESPACE("xnu.vm"),
34*4f1223e8SApple OSS Distributions T_META_RADAR_COMPONENT_NAME("xnu"),
35*4f1223e8SApple OSS Distributions T_META_RADAR_COMPONENT_VERSION("performance"),
36*4f1223e8SApple OSS Distributions T_META_OWNER("jarrad"),
37*4f1223e8SApple OSS Distributions T_META_ENABLED(TARGET_OS_IOS && !TARGET_OS_MACCATALYST),
38*4f1223e8SApple OSS Distributions // Ensure we don't conflict with libmalloc's reclaim buffer
39*4f1223e8SApple OSS Distributions T_META_ENVVAR("MallocLargeCache=0"),
40*4f1223e8SApple OSS Distributions T_META_RUN_CONCURRENTLY(false)
41*4f1223e8SApple OSS Distributions );
42*4f1223e8SApple OSS Distributions
43*4f1223e8SApple OSS Distributions T_DECL(vm_reclaim_init, "Set up and tear down a reclaim buffer",
44*4f1223e8SApple OSS Distributions T_META_SYSCTL_INT(VM_RECLAIM_THRESHOLD_SYSCTL_LOW),
45*4f1223e8SApple OSS Distributions T_META_TAG_VM_PREFERRED)
46*4f1223e8SApple OSS Distributions {
47*4f1223e8SApple OSS Distributions struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
48*4f1223e8SApple OSS Distributions
49*4f1223e8SApple OSS Distributions kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
50*4f1223e8SApple OSS Distributions
51*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
52*4f1223e8SApple OSS Distributions }
53*4f1223e8SApple OSS Distributions
54*4f1223e8SApple OSS Distributions T_DECL(vm_reclaim_init_fails_when_disabled, "Initializing a ring buffer on a system with vm_reclaim disabled should fail",
55*4f1223e8SApple OSS Distributions T_META_BOOTARGS_SET(VM_RECLAIM_BOOTARG_DISABLED), T_META_TAG_VM_PREFERRED)
56*4f1223e8SApple OSS Distributions {
57*4f1223e8SApple OSS Distributions struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
58*4f1223e8SApple OSS Distributions
59*4f1223e8SApple OSS Distributions kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
60*4f1223e8SApple OSS Distributions
61*4f1223e8SApple OSS Distributions T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_NOT_SUPPORTED, "mach_vm_reclaim_ringbuffer_init");
62*4f1223e8SApple OSS Distributions }
63*4f1223e8SApple OSS Distributions
64*4f1223e8SApple OSS Distributions /*
65*4f1223e8SApple OSS Distributions * Allocate a buffer of the given size, write val to each byte, and free it via a deferred free call.
66*4f1223e8SApple OSS Distributions */
67*4f1223e8SApple OSS Distributions static uint64_t
allocate_and_defer_free(size_t size,mach_vm_reclaim_ringbuffer_v1_t ringbuffer,unsigned char val,mach_vm_reclaim_behavior_v1_t behavior,mach_vm_address_t * addr)68*4f1223e8SApple OSS Distributions allocate_and_defer_free(size_t size, mach_vm_reclaim_ringbuffer_v1_t ringbuffer, unsigned char val, mach_vm_reclaim_behavior_v1_t behavior, mach_vm_address_t *addr /* OUT */)
69*4f1223e8SApple OSS Distributions {
70*4f1223e8SApple OSS Distributions kern_return_t kr = mach_vm_map(mach_task_self(), addr, size, 0, VM_FLAGS_ANYWHERE, MEMORY_OBJECT_NULL, 0, FALSE, VM_PROT_DEFAULT, VM_PROT_ALL, VM_INHERIT_DEFAULT);
71*4f1223e8SApple OSS Distributions bool should_update_kernel_accounting = false;
72*4f1223e8SApple OSS Distributions uint64_t idx;
73*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_map");
74*4f1223e8SApple OSS Distributions
75*4f1223e8SApple OSS Distributions memset((void *) *addr, val, size);
76*4f1223e8SApple OSS Distributions
77*4f1223e8SApple OSS Distributions idx = mach_vm_reclaim_mark_free(ringbuffer, *addr, (uint32_t) size, behavior, &should_update_kernel_accounting);
78*4f1223e8SApple OSS Distributions if (should_update_kernel_accounting) {
79*4f1223e8SApple OSS Distributions mach_vm_reclaim_update_kernel_accounting(ringbuffer);
80*4f1223e8SApple OSS Distributions }
81*4f1223e8SApple OSS Distributions return idx;
82*4f1223e8SApple OSS Distributions }
83*4f1223e8SApple OSS Distributions
84*4f1223e8SApple OSS Distributions static uint64_t
allocate_and_defer_deallocate(size_t size,mach_vm_reclaim_ringbuffer_v1_t ringbuffer,unsigned char val,mach_vm_address_t * addr)85*4f1223e8SApple OSS Distributions allocate_and_defer_deallocate(size_t size, mach_vm_reclaim_ringbuffer_v1_t ringbuffer, unsigned char val, mach_vm_address_t *addr /* OUT */)
86*4f1223e8SApple OSS Distributions {
87*4f1223e8SApple OSS Distributions return allocate_and_defer_free(size, ringbuffer, val, MACH_VM_RECLAIM_DEALLOCATE, addr);
88*4f1223e8SApple OSS Distributions }
89*4f1223e8SApple OSS Distributions
90*4f1223e8SApple OSS Distributions T_DECL(vm_reclaim_single_entry, "Place a single entry in the buffer and call sync",
91*4f1223e8SApple OSS Distributions T_META_SYSCTL_INT(VM_RECLAIM_THRESHOLD_SYSCTL_LOW),
92*4f1223e8SApple OSS Distributions T_META_TAG_VM_PREFERRED)
93*4f1223e8SApple OSS Distributions {
94*4f1223e8SApple OSS Distributions struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
95*4f1223e8SApple OSS Distributions static const size_t kAllocationSize = (1UL << 20); // 1MB
96*4f1223e8SApple OSS Distributions mach_vm_address_t addr;
97*4f1223e8SApple OSS Distributions
98*4f1223e8SApple OSS Distributions kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
99*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
100*4f1223e8SApple OSS Distributions
101*4f1223e8SApple OSS Distributions uint64_t idx = allocate_and_defer_deallocate(kAllocationSize, &ringbuffer, 1, &addr);
102*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(idx, 0ULL, "Entry placed at start of buffer");
103*4f1223e8SApple OSS Distributions mach_vm_reclaim_synchronize(&ringbuffer, 1);
104*4f1223e8SApple OSS Distributions }
105*4f1223e8SApple OSS Distributions
106*4f1223e8SApple OSS Distributions static pid_t
spawn_helper(char * helper)107*4f1223e8SApple OSS Distributions spawn_helper(char *helper)
108*4f1223e8SApple OSS Distributions {
109*4f1223e8SApple OSS Distributions char **launch_tool_args;
110*4f1223e8SApple OSS Distributions char testpath[PATH_MAX];
111*4f1223e8SApple OSS Distributions uint32_t testpath_buf_size;
112*4f1223e8SApple OSS Distributions pid_t child_pid;
113*4f1223e8SApple OSS Distributions
114*4f1223e8SApple OSS Distributions testpath_buf_size = sizeof(testpath);
115*4f1223e8SApple OSS Distributions int ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
116*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
117*4f1223e8SApple OSS Distributions T_LOG("Executable path: %s", testpath);
118*4f1223e8SApple OSS Distributions launch_tool_args = (char *[]){
119*4f1223e8SApple OSS Distributions testpath,
120*4f1223e8SApple OSS Distributions "-n",
121*4f1223e8SApple OSS Distributions helper,
122*4f1223e8SApple OSS Distributions NULL
123*4f1223e8SApple OSS Distributions };
124*4f1223e8SApple OSS Distributions
125*4f1223e8SApple OSS Distributions /* Spawn the child process. */
126*4f1223e8SApple OSS Distributions ret = dt_launch_tool(&child_pid, launch_tool_args, false, NULL, NULL);
127*4f1223e8SApple OSS Distributions if (ret != 0) {
128*4f1223e8SApple OSS Distributions T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
129*4f1223e8SApple OSS Distributions }
130*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_POSIX_SUCCESS(child_pid, "dt_launch_tool");
131*4f1223e8SApple OSS Distributions
132*4f1223e8SApple OSS Distributions return child_pid;
133*4f1223e8SApple OSS Distributions }
134*4f1223e8SApple OSS Distributions
135*4f1223e8SApple OSS Distributions static int
spawn_helper_and_wait_for_exit(char * helper)136*4f1223e8SApple OSS Distributions spawn_helper_and_wait_for_exit(char *helper)
137*4f1223e8SApple OSS Distributions {
138*4f1223e8SApple OSS Distributions int status;
139*4f1223e8SApple OSS Distributions pid_t child_pid, rc;
140*4f1223e8SApple OSS Distributions
141*4f1223e8SApple OSS Distributions child_pid = spawn_helper(helper);
142*4f1223e8SApple OSS Distributions rc = waitpid(child_pid, &status, 0);
143*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
144*4f1223e8SApple OSS Distributions return status;
145*4f1223e8SApple OSS Distributions }
146*4f1223e8SApple OSS Distributions
147*4f1223e8SApple OSS Distributions /*
148*4f1223e8SApple OSS Distributions * Returns true iff every entry in buffer is expected.
149*4f1223e8SApple OSS Distributions */
150*4f1223e8SApple OSS Distributions static bool
check_buffer(mach_vm_address_t addr,size_t size,unsigned char expected)151*4f1223e8SApple OSS Distributions check_buffer(mach_vm_address_t addr, size_t size, unsigned char expected)
152*4f1223e8SApple OSS Distributions {
153*4f1223e8SApple OSS Distributions unsigned char *buffer = (unsigned char *) addr;
154*4f1223e8SApple OSS Distributions for (size_t i = 0; i < size; i++) {
155*4f1223e8SApple OSS Distributions if (buffer[i] != expected) {
156*4f1223e8SApple OSS Distributions return false;
157*4f1223e8SApple OSS Distributions }
158*4f1223e8SApple OSS Distributions }
159*4f1223e8SApple OSS Distributions return true;
160*4f1223e8SApple OSS Distributions }
161*4f1223e8SApple OSS Distributions
162*4f1223e8SApple OSS Distributions /*
163*4f1223e8SApple OSS Distributions * Read every byte of a buffer to ensure re-usability
164*4f1223e8SApple OSS Distributions */
165*4f1223e8SApple OSS Distributions static void
read_buffer(mach_vm_address_t addr,size_t size)166*4f1223e8SApple OSS Distributions read_buffer(mach_vm_address_t addr, size_t size)
167*4f1223e8SApple OSS Distributions {
168*4f1223e8SApple OSS Distributions volatile uint8_t byte;
169*4f1223e8SApple OSS Distributions uint8_t *buffer = (uint8_t *)addr;
170*4f1223e8SApple OSS Distributions for (size_t i = 0; i < size; i++) {
171*4f1223e8SApple OSS Distributions byte = buffer[i];
172*4f1223e8SApple OSS Distributions }
173*4f1223e8SApple OSS Distributions }
174*4f1223e8SApple OSS Distributions
175*4f1223e8SApple OSS Distributions /*
176*4f1223e8SApple OSS Distributions * Check that the given (freed) buffer has changed.
177*4f1223e8SApple OSS Distributions * This will likely crash, but if we make it through the entire buffer then segfault on purpose.
178*4f1223e8SApple OSS Distributions */
179*4f1223e8SApple OSS Distributions static void
assert_buffer_has_changed_and_crash(mach_vm_address_t addr,size_t size,unsigned char expected)180*4f1223e8SApple OSS Distributions assert_buffer_has_changed_and_crash(mach_vm_address_t addr, size_t size, unsigned char expected)
181*4f1223e8SApple OSS Distributions {
182*4f1223e8SApple OSS Distributions /*
183*4f1223e8SApple OSS Distributions * mach_vm_reclaim_synchronize should have ensured the buffer was freed.
184*4f1223e8SApple OSS Distributions * Two cases:
185*4f1223e8SApple OSS Distributions * 1. The buffer is still free (touching it causes a crash)
186*4f1223e8SApple OSS Distributions * 2. The address range was re-allocated by some other library in process.
187*4f1223e8SApple OSS Distributions * #1 is far more likely. But if #2 happened, the buffer shouldn't be filled
188*4f1223e8SApple OSS Distributions * with the value we wrote to it. So scan the buffer. If we segfault it's case #1
189*4f1223e8SApple OSS Distributions * and if we see another value it's case #2.
190*4f1223e8SApple OSS Distributions */
191*4f1223e8SApple OSS Distributions bool changed = !check_buffer(addr, size, expected);
192*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_TRUE(changed, "buffer was re-allocated");
193*4f1223e8SApple OSS Distributions /* Case #2. Force a segfault so the parent sees that we crashed. */
194*4f1223e8SApple OSS Distributions *(volatile int *) 0 = 1;
195*4f1223e8SApple OSS Distributions
196*4f1223e8SApple OSS Distributions T_FAIL("Test did not crash when dereferencing NULL");
197*4f1223e8SApple OSS Distributions }
198*4f1223e8SApple OSS Distributions
199*4f1223e8SApple OSS Distributions static void
reuse_reclaimed_entry(mach_vm_reclaim_behavior_v1_t behavior)200*4f1223e8SApple OSS Distributions reuse_reclaimed_entry(mach_vm_reclaim_behavior_v1_t behavior)
201*4f1223e8SApple OSS Distributions {
202*4f1223e8SApple OSS Distributions struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
203*4f1223e8SApple OSS Distributions static const size_t kAllocationSize = (1UL << 20); // 1MB
204*4f1223e8SApple OSS Distributions mach_vm_address_t addr;
205*4f1223e8SApple OSS Distributions static const unsigned char kValue = 220;
206*4f1223e8SApple OSS Distributions
207*4f1223e8SApple OSS Distributions kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
208*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
209*4f1223e8SApple OSS Distributions
210*4f1223e8SApple OSS Distributions uint64_t idx = allocate_and_defer_free(kAllocationSize, &ringbuffer, kValue, behavior, &addr);
211*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(idx, 0ULL, "Entry placed at start of buffer");
212*4f1223e8SApple OSS Distributions kr = mach_vm_reclaim_synchronize(&ringbuffer, 10);
213*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_synchronize");
214*4f1223e8SApple OSS Distributions bool usable = mach_vm_reclaim_mark_used(&ringbuffer, idx, addr, kAllocationSize);
215*4f1223e8SApple OSS Distributions bool reclaimed = mach_vm_reclaim_is_reclaimed(&ringbuffer, idx);
216*4f1223e8SApple OSS Distributions T_EXPECT_FALSE(usable, "reclaimed entry is not re-usable");
217*4f1223e8SApple OSS Distributions T_EXPECT_TRUE(reclaimed, "reclaimed entry was marked reclaimed");
218*4f1223e8SApple OSS Distributions switch (behavior) {
219*4f1223e8SApple OSS Distributions case MACH_VM_RECLAIM_DEALLOCATE:
220*4f1223e8SApple OSS Distributions assert_buffer_has_changed_and_crash(addr, kAllocationSize, kValue);
221*4f1223e8SApple OSS Distributions break;
222*4f1223e8SApple OSS Distributions case MACH_VM_RECLAIM_REUSABLE:
223*4f1223e8SApple OSS Distributions read_buffer(addr, kAllocationSize);
224*4f1223e8SApple OSS Distributions T_PASS("Freed buffer re-used successfully");
225*4f1223e8SApple OSS Distributions break;
226*4f1223e8SApple OSS Distributions default:
227*4f1223e8SApple OSS Distributions T_FAIL("Unexpected reclaim behavior %d", behavior);
228*4f1223e8SApple OSS Distributions }
229*4f1223e8SApple OSS Distributions }
230*4f1223e8SApple OSS Distributions
231*4f1223e8SApple OSS Distributions T_HELPER_DECL(reuse_freed_entry_dealloc,
232*4f1223e8SApple OSS Distributions "defer free (dealloc), sync, and try to use entry")
233*4f1223e8SApple OSS Distributions {
234*4f1223e8SApple OSS Distributions reuse_reclaimed_entry(MACH_VM_RECLAIM_DEALLOCATE);
235*4f1223e8SApple OSS Distributions }
236*4f1223e8SApple OSS Distributions
237*4f1223e8SApple OSS Distributions T_HELPER_DECL(reuse_freed_entry_reusable,
238*4f1223e8SApple OSS Distributions "defer free (reusable), sync, and try to use entry")
239*4f1223e8SApple OSS Distributions {
240*4f1223e8SApple OSS Distributions reuse_reclaimed_entry(MACH_VM_RECLAIM_REUSABLE);
241*4f1223e8SApple OSS Distributions }
242*4f1223e8SApple OSS Distributions
243*4f1223e8SApple OSS Distributions T_DECL(vm_reclaim_single_entry_verify_free, "Place a single entry in the buffer and call sync",
244*4f1223e8SApple OSS Distributions T_META_IGNORECRASHES(".*vm_reclaim_single_entry_verify_free.*"),
245*4f1223e8SApple OSS Distributions T_META_SYSCTL_INT(VM_RECLAIM_THRESHOLD_SYSCTL_HIGH),
246*4f1223e8SApple OSS Distributions T_META_TAG_VM_PREFERRED)
247*4f1223e8SApple OSS Distributions {
248*4f1223e8SApple OSS Distributions int status = spawn_helper_and_wait_for_exit("reuse_freed_entry_dealloc");
249*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(status), "Test process crashed.");
250*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(WTERMSIG(status), SIGSEGV, "Test process crashed with segmentation fault.");
251*4f1223e8SApple OSS Distributions }
252*4f1223e8SApple OSS Distributions
253*4f1223e8SApple OSS Distributions T_DECL(vm_reclaim_single_entry_reusable,
254*4f1223e8SApple OSS Distributions "Reclaim a reusable entry and verify re-use is legal",
255*4f1223e8SApple OSS Distributions T_META_SYSCTL_INT(VM_RECLAIM_THRESHOLD_SYSCTL_HIGH),
256*4f1223e8SApple OSS Distributions T_META_TAG_VM_PREFERRED)
257*4f1223e8SApple OSS Distributions {
258*4f1223e8SApple OSS Distributions int status = spawn_helper_and_wait_for_exit("reuse_freed_entry_reusable");
259*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_TRUE(WIFEXITED(status), "Test process exited.");
260*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(WEXITSTATUS(status), 0, "Test process exited cleanly.");
261*4f1223e8SApple OSS Distributions }
262*4f1223e8SApple OSS Distributions
263*4f1223e8SApple OSS Distributions static void
allocate_and_suspend(char * const * argv,bool free_buffer,bool double_free)264*4f1223e8SApple OSS Distributions allocate_and_suspend(char *const *argv, bool free_buffer, bool double_free)
265*4f1223e8SApple OSS Distributions {
266*4f1223e8SApple OSS Distributions struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
267*4f1223e8SApple OSS Distributions static const size_t kAllocationSize = (1UL << 20); // 1MB
268*4f1223e8SApple OSS Distributions mach_vm_address_t addr = 0;
269*4f1223e8SApple OSS Distributions bool should_update_kernel_accounting = false;
270*4f1223e8SApple OSS Distributions
271*4f1223e8SApple OSS Distributions const mach_vm_size_t kNumEntries = (size_t) atoi(argv[0]);
272*4f1223e8SApple OSS Distributions
273*4f1223e8SApple OSS Distributions kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
274*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
275*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_LT(kNumEntries, ringbuffer.buffer_len, "Test does not fill up ringubffer");
276*4f1223e8SApple OSS Distributions
277*4f1223e8SApple OSS Distributions for (size_t i = 0; i < kNumEntries; i++) {
278*4f1223e8SApple OSS Distributions uint64_t idx = allocate_and_defer_deallocate(kAllocationSize, &ringbuffer, (unsigned char) i, &addr);
279*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(idx, (uint64_t) i, "idx is correct");
280*4f1223e8SApple OSS Distributions }
281*4f1223e8SApple OSS Distributions
282*4f1223e8SApple OSS Distributions if (double_free) {
283*4f1223e8SApple OSS Distributions // Double free the last entry
284*4f1223e8SApple OSS Distributions mach_vm_reclaim_mark_free(&ringbuffer, addr, (uint32_t) kAllocationSize, MACH_VM_RECLAIM_DEALLOCATE, &should_update_kernel_accounting);
285*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_mark_free");
286*4f1223e8SApple OSS Distributions }
287*4f1223e8SApple OSS Distributions
288*4f1223e8SApple OSS Distributions if (free_buffer) {
289*4f1223e8SApple OSS Distributions mach_vm_size_t buffer_size = ringbuffer.buffer_len * sizeof(mach_vm_reclaim_entry_v1_t) + \
290*4f1223e8SApple OSS Distributions offsetof(struct mach_vm_reclaim_buffer_v1_s, entries);
291*4f1223e8SApple OSS Distributions kr = mach_vm_deallocate(mach_task_self(), (mach_vm_address_t) ringbuffer.buffer, buffer_size);
292*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_deallocate");
293*4f1223e8SApple OSS Distributions }
294*4f1223e8SApple OSS Distributions
295*4f1223e8SApple OSS Distributions // Signal to our parent to suspend us
296*4f1223e8SApple OSS Distributions if (kill(getppid(), SIGUSR1) != 0) {
297*4f1223e8SApple OSS Distributions T_LOG("Unable to signal to parent process!");
298*4f1223e8SApple OSS Distributions exit(1);
299*4f1223e8SApple OSS Distributions }
300*4f1223e8SApple OSS Distributions
301*4f1223e8SApple OSS Distributions while (1) {
302*4f1223e8SApple OSS Distributions ;
303*4f1223e8SApple OSS Distributions }
304*4f1223e8SApple OSS Distributions }
305*4f1223e8SApple OSS Distributions
306*4f1223e8SApple OSS Distributions T_HELPER_DECL(allocate_and_suspend,
307*4f1223e8SApple OSS Distributions "defer free, and signal parent to suspend")
308*4f1223e8SApple OSS Distributions {
309*4f1223e8SApple OSS Distributions allocate_and_suspend(argv, false, false);
310*4f1223e8SApple OSS Distributions }
311*4f1223e8SApple OSS Distributions
312*4f1223e8SApple OSS Distributions static void
resume_and_kill_proc(pid_t pid)313*4f1223e8SApple OSS Distributions resume_and_kill_proc(pid_t pid)
314*4f1223e8SApple OSS Distributions {
315*4f1223e8SApple OSS Distributions int ret = pid_resume(pid);
316*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "proc resumed after freeze");
317*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(pid, SIGKILL), "Killed process");
318*4f1223e8SApple OSS Distributions }
319*4f1223e8SApple OSS Distributions
320*4f1223e8SApple OSS Distributions static void
drain_async_queue(pid_t child_pid)321*4f1223e8SApple OSS Distributions drain_async_queue(pid_t child_pid)
322*4f1223e8SApple OSS Distributions {
323*4f1223e8SApple OSS Distributions int val = child_pid;
324*4f1223e8SApple OSS Distributions int ret;
325*4f1223e8SApple OSS Distributions size_t len = sizeof(val);
326*4f1223e8SApple OSS Distributions ret = sysctlbyname("vm.reclaim_drain_async_queue", NULL, NULL, &val, len);
327*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "vm.reclaim_drain_async_queue");
328*4f1223e8SApple OSS Distributions }
329*4f1223e8SApple OSS Distributions
330*4f1223e8SApple OSS Distributions static size_t
ledger_phys_footprint_index(size_t * num_entries)331*4f1223e8SApple OSS Distributions ledger_phys_footprint_index(size_t *num_entries)
332*4f1223e8SApple OSS Distributions {
333*4f1223e8SApple OSS Distributions struct ledger_info li;
334*4f1223e8SApple OSS Distributions struct ledger_template_info *templateInfo = NULL;
335*4f1223e8SApple OSS Distributions int ret;
336*4f1223e8SApple OSS Distributions size_t i, footprint_index;
337*4f1223e8SApple OSS Distributions bool found = false;
338*4f1223e8SApple OSS Distributions
339*4f1223e8SApple OSS Distributions ret = ledger(LEDGER_INFO, (caddr_t)(uintptr_t)getpid(), (caddr_t)&li, NULL);
340*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "ledger(LEDGER_INFO)");
341*4f1223e8SApple OSS Distributions
342*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_GT(li.li_entries, (int64_t) 0, "num ledger entries is valid");
343*4f1223e8SApple OSS Distributions *num_entries = (size_t) li.li_entries;
344*4f1223e8SApple OSS Distributions templateInfo = malloc((size_t)li.li_entries * sizeof(struct ledger_template_info));
345*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_NOTNULL(templateInfo, "malloc entries");
346*4f1223e8SApple OSS Distributions
347*4f1223e8SApple OSS Distributions footprint_index = 0;
348*4f1223e8SApple OSS Distributions ret = ledger(LEDGER_TEMPLATE_INFO, (caddr_t) templateInfo, (caddr_t) num_entries, NULL);
349*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "ledger(LEDGER_TEMPLATE_INFO)");
350*4f1223e8SApple OSS Distributions for (i = 0; i < *num_entries; i++) {
351*4f1223e8SApple OSS Distributions if (strcmp(templateInfo[i].lti_name, "phys_footprint") == 0) {
352*4f1223e8SApple OSS Distributions footprint_index = i;
353*4f1223e8SApple OSS Distributions found = true;
354*4f1223e8SApple OSS Distributions }
355*4f1223e8SApple OSS Distributions }
356*4f1223e8SApple OSS Distributions free(templateInfo);
357*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_TRUE(found, "found phys_footprint in ledger");
358*4f1223e8SApple OSS Distributions return footprint_index;
359*4f1223e8SApple OSS Distributions }
360*4f1223e8SApple OSS Distributions
361*4f1223e8SApple OSS Distributions static int64_t
get_ledger_entry_for_pid(pid_t pid,size_t index,size_t num_entries)362*4f1223e8SApple OSS Distributions get_ledger_entry_for_pid(pid_t pid, size_t index, size_t num_entries)
363*4f1223e8SApple OSS Distributions {
364*4f1223e8SApple OSS Distributions int ret;
365*4f1223e8SApple OSS Distributions int64_t value;
366*4f1223e8SApple OSS Distributions struct ledger_entry_info *lei = NULL;
367*4f1223e8SApple OSS Distributions
368*4f1223e8SApple OSS Distributions lei = malloc(num_entries * sizeof(*lei));
369*4f1223e8SApple OSS Distributions ret = ledger(LEDGER_ENTRY_INFO, (caddr_t) (uintptr_t) pid, (caddr_t) lei, (caddr_t) &num_entries);
370*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "ledger(LEDGER_ENTRY_INFO)");
371*4f1223e8SApple OSS Distributions value = lei[index].lei_balance;
372*4f1223e8SApple OSS Distributions free(lei);
373*4f1223e8SApple OSS Distributions return value;
374*4f1223e8SApple OSS Distributions }
375*4f1223e8SApple OSS Distributions
376*4f1223e8SApple OSS Distributions static pid_t child_pid;
377*4f1223e8SApple OSS Distributions
378*4f1223e8SApple OSS Distributions static void
test_after_background_helper_launches(char * variant,char * arg1,dispatch_block_t test_block,dispatch_block_t exit_block)379*4f1223e8SApple OSS Distributions test_after_background_helper_launches(char* variant, char * arg1, dispatch_block_t test_block, dispatch_block_t exit_block)
380*4f1223e8SApple OSS Distributions {
381*4f1223e8SApple OSS Distributions char **launch_tool_args;
382*4f1223e8SApple OSS Distributions char testpath[PATH_MAX];
383*4f1223e8SApple OSS Distributions uint32_t testpath_buf_size;
384*4f1223e8SApple OSS Distributions
385*4f1223e8SApple OSS Distributions dispatch_source_t ds_signal, ds_exit;
386*4f1223e8SApple OSS Distributions
387*4f1223e8SApple OSS Distributions /* Wait for the child process to tell us that it's ready, and then freeze it */
388*4f1223e8SApple OSS Distributions signal(SIGUSR1, SIG_IGN);
389*4f1223e8SApple OSS Distributions ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
390*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create");
391*4f1223e8SApple OSS Distributions dispatch_source_set_event_handler(ds_signal, test_block);
392*4f1223e8SApple OSS Distributions
393*4f1223e8SApple OSS Distributions dispatch_activate(ds_signal);
394*4f1223e8SApple OSS Distributions
395*4f1223e8SApple OSS Distributions testpath_buf_size = sizeof(testpath);
396*4f1223e8SApple OSS Distributions int ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
397*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
398*4f1223e8SApple OSS Distributions T_LOG("Executable path: %s", testpath);
399*4f1223e8SApple OSS Distributions launch_tool_args = (char *[]){
400*4f1223e8SApple OSS Distributions testpath,
401*4f1223e8SApple OSS Distributions "-n",
402*4f1223e8SApple OSS Distributions variant,
403*4f1223e8SApple OSS Distributions arg1,
404*4f1223e8SApple OSS Distributions NULL
405*4f1223e8SApple OSS Distributions };
406*4f1223e8SApple OSS Distributions
407*4f1223e8SApple OSS Distributions /* Spawn the child process. */
408*4f1223e8SApple OSS Distributions ret = dt_launch_tool(&child_pid, launch_tool_args, false, NULL, NULL);
409*4f1223e8SApple OSS Distributions if (ret != 0) {
410*4f1223e8SApple OSS Distributions T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
411*4f1223e8SApple OSS Distributions }
412*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_POSIX_SUCCESS(child_pid, "dt_launch_tool");
413*4f1223e8SApple OSS Distributions
414*4f1223e8SApple OSS Distributions /* Listen for exit. */
415*4f1223e8SApple OSS Distributions ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
416*4f1223e8SApple OSS Distributions dispatch_source_set_event_handler(ds_exit, exit_block);
417*4f1223e8SApple OSS Distributions
418*4f1223e8SApple OSS Distributions dispatch_activate(ds_exit);
419*4f1223e8SApple OSS Distributions dispatch_main();
420*4f1223e8SApple OSS Distributions }
421*4f1223e8SApple OSS Distributions
422*4f1223e8SApple OSS Distributions T_DECL(vm_reclaim_full_reclaim_on_suspend, "Defer free memory and then suspend.",
423*4f1223e8SApple OSS Distributions T_META_ASROOT(true),
424*4f1223e8SApple OSS Distributions T_META_SYSCTL_INT(VM_RECLAIM_THRESHOLD_SYSCTL_HIGH),
425*4f1223e8SApple OSS Distributions T_META_TAG_VM_PREFERRED)
426*4f1223e8SApple OSS Distributions {
427*4f1223e8SApple OSS Distributions test_after_background_helper_launches("allocate_and_suspend", "20", ^{
428*4f1223e8SApple OSS Distributions int ret = 0;
429*4f1223e8SApple OSS Distributions size_t num_ledger_entries = 0;
430*4f1223e8SApple OSS Distributions size_t phys_footprint_index = ledger_phys_footprint_index(&num_ledger_entries);
431*4f1223e8SApple OSS Distributions int64_t before_footprint, after_footprint, reclaimable_bytes = 20 * (1ULL << 20);
432*4f1223e8SApple OSS Distributions before_footprint = get_ledger_entry_for_pid(child_pid, phys_footprint_index, num_ledger_entries);
433*4f1223e8SApple OSS Distributions T_QUIET; T_EXPECT_GE(before_footprint, reclaimable_bytes, "memory was allocated");
434*4f1223e8SApple OSS Distributions ret = pid_suspend(child_pid);
435*4f1223e8SApple OSS Distributions T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
436*4f1223e8SApple OSS Distributions /*
437*4f1223e8SApple OSS Distributions * The reclaim work is kicked off asynchronously by the suspend.
438*4f1223e8SApple OSS Distributions * So we need to call into the kernel to synchronize with the reclaim worker
439*4f1223e8SApple OSS Distributions * thread.
440*4f1223e8SApple OSS Distributions */
441*4f1223e8SApple OSS Distributions drain_async_queue(child_pid);
442*4f1223e8SApple OSS Distributions
443*4f1223e8SApple OSS Distributions after_footprint = get_ledger_entry_for_pid(child_pid, phys_footprint_index, num_ledger_entries);
444*4f1223e8SApple OSS Distributions T_QUIET; T_EXPECT_LE(after_footprint, before_footprint - reclaimable_bytes, "memory was reclaimed");
445*4f1223e8SApple OSS Distributions
446*4f1223e8SApple OSS Distributions resume_and_kill_proc(child_pid);
447*4f1223e8SApple OSS Distributions },
448*4f1223e8SApple OSS Distributions ^{
449*4f1223e8SApple OSS Distributions int status = 0, code = 0;
450*4f1223e8SApple OSS Distributions pid_t rc = waitpid(child_pid, &status, 0);
451*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
452*4f1223e8SApple OSS Distributions code = WEXITSTATUS(status);
453*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly");
454*4f1223e8SApple OSS Distributions T_END;
455*4f1223e8SApple OSS Distributions });
456*4f1223e8SApple OSS Distributions }
457*4f1223e8SApple OSS Distributions
458*4f1223e8SApple OSS Distributions T_DECL(vm_reclaim_limit_kills, "Deferred reclaims are processed before a limit kill",
459*4f1223e8SApple OSS Distributions T_META_SYSCTL_INT(VM_RECLAIM_THRESHOLD_SYSCTL_LOW),
460*4f1223e8SApple OSS Distributions T_META_TAG_VM_PREFERRED)
461*4f1223e8SApple OSS Distributions {
462*4f1223e8SApple OSS Distributions int err;
463*4f1223e8SApple OSS Distributions struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
464*4f1223e8SApple OSS Distributions const size_t kNumEntries = 50;
465*4f1223e8SApple OSS Distributions static const size_t kAllocationSize = (1UL << 20); // 1MB
466*4f1223e8SApple OSS Distributions static const size_t kMemoryLimit = kNumEntries / 10 * kAllocationSize;
467*4f1223e8SApple OSS Distributions
468*4f1223e8SApple OSS Distributions kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
469*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
470*4f1223e8SApple OSS Distributions
471*4f1223e8SApple OSS Distributions err = set_memlimits(getpid(), kMemoryLimit >> 20, kMemoryLimit >> 20, TRUE, TRUE);
472*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_POSIX_SUCCESS(err, "set_memlimits");
473*4f1223e8SApple OSS Distributions
474*4f1223e8SApple OSS Distributions for (size_t i = 0; i < kNumEntries; i++) {
475*4f1223e8SApple OSS Distributions mach_vm_address_t addr = 0;
476*4f1223e8SApple OSS Distributions uint64_t idx = allocate_and_defer_deallocate(kAllocationSize, &ringbuffer, (unsigned char) i, &addr);
477*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(idx, (uint64_t) i, "idx is correct");
478*4f1223e8SApple OSS Distributions }
479*4f1223e8SApple OSS Distributions
480*4f1223e8SApple OSS Distributions T_PASS("Was able to allocate and defer free %zu chunks of size %zu bytes while staying under limit of %zu bytes", kNumEntries, kAllocationSize, kMemoryLimit);
481*4f1223e8SApple OSS Distributions }
482*4f1223e8SApple OSS Distributions
483*4f1223e8SApple OSS Distributions T_DECL(vm_reclaim_update_reclaimable_bytes_threshold, "Kernel reclaims when num_bytes_reclaimable crosses threshold",
484*4f1223e8SApple OSS Distributions T_META_SYSCTL_INT(VM_RECLAIM_THRESHOLD_SYSCTL_LOW),
485*4f1223e8SApple OSS Distributions T_META_TAG_VM_PREFERRED)
486*4f1223e8SApple OSS Distributions {
487*4f1223e8SApple OSS Distributions mach_vm_size_t kNumEntries = 0;
488*4f1223e8SApple OSS Distributions struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
489*4f1223e8SApple OSS Distributions const size_t kAllocationSize = vm_kernel_page_size;
490*4f1223e8SApple OSS Distributions uint64_t vm_reclaim_reclaimable_max_threshold;
491*4f1223e8SApple OSS Distributions int ret;
492*4f1223e8SApple OSS Distributions size_t len = sizeof(vm_reclaim_reclaimable_max_threshold);
493*4f1223e8SApple OSS Distributions size_t num_ledger_entries = 0;
494*4f1223e8SApple OSS Distributions size_t phys_footprint_index = ledger_phys_footprint_index(&num_ledger_entries);
495*4f1223e8SApple OSS Distributions
496*4f1223e8SApple OSS Distributions kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
497*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
498*4f1223e8SApple OSS Distributions
499*4f1223e8SApple OSS Distributions // Allocate 1000 times the reclaim threshold
500*4f1223e8SApple OSS Distributions ret = sysctlbyname("vm.reclaim_max_threshold", &vm_reclaim_reclaimable_max_threshold, &len, NULL, 0);
501*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "vm.reclaim_max_threshold");
502*4f1223e8SApple OSS Distributions kNumEntries = vm_reclaim_reclaimable_max_threshold / kAllocationSize * 1000;
503*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_LT(kNumEntries, ringbuffer.buffer_len, "Entries will not fill up ringbuffer.");
504*4f1223e8SApple OSS Distributions
505*4f1223e8SApple OSS Distributions mach_vm_address_t addr = 0;
506*4f1223e8SApple OSS Distributions for (uint64_t i = 0; i < kNumEntries; i++) {
507*4f1223e8SApple OSS Distributions uint64_t idx = allocate_and_defer_deallocate(kAllocationSize, &ringbuffer, (unsigned char) i, &addr);
508*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(idx, i, "idx is correct");
509*4f1223e8SApple OSS Distributions }
510*4f1223e8SApple OSS Distributions
511*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_LT(get_ledger_entry_for_pid(getpid(), phys_footprint_index, num_ledger_entries),
512*4f1223e8SApple OSS Distributions (int64_t) ((kNumEntries) * kAllocationSize), "Entries were reclaimed as we crossed threshold");
513*4f1223e8SApple OSS Distributions }
514*4f1223e8SApple OSS Distributions
515*4f1223e8SApple OSS Distributions T_HELPER_DECL(deallocate_buffer,
516*4f1223e8SApple OSS Distributions "deallocate the buffer from underneath the kernel")
517*4f1223e8SApple OSS Distributions {
518*4f1223e8SApple OSS Distributions struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
519*4f1223e8SApple OSS Distributions static const size_t kAllocationSize = (1UL << 20); // 1MB
520*4f1223e8SApple OSS Distributions mach_vm_address_t addr;
521*4f1223e8SApple OSS Distributions
522*4f1223e8SApple OSS Distributions kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
523*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
524*4f1223e8SApple OSS Distributions
525*4f1223e8SApple OSS Distributions uint64_t idx = allocate_and_defer_deallocate(kAllocationSize, &ringbuffer, 1, &addr);
526*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(idx, 0ULL, "Entry placed at start of buffer");
527*4f1223e8SApple OSS Distributions mach_vm_size_t buffer_size = ringbuffer.buffer_len * sizeof(mach_vm_reclaim_entry_v1_t) + \
528*4f1223e8SApple OSS Distributions offsetof(struct mach_vm_reclaim_buffer_v1_s, entries);
529*4f1223e8SApple OSS Distributions kr = mach_vm_deallocate(mach_task_self(), (mach_vm_address_t) ringbuffer.buffer, buffer_size);
530*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_deallocate");
531*4f1223e8SApple OSS Distributions
532*4f1223e8SApple OSS Distributions mach_vm_reclaim_synchronize(&ringbuffer, 10);
533*4f1223e8SApple OSS Distributions
534*4f1223e8SApple OSS Distributions T_FAIL("Test did not crash when synchronizing on a deallocated buffer!");
535*4f1223e8SApple OSS Distributions }
536*4f1223e8SApple OSS Distributions
537*4f1223e8SApple OSS Distributions T_DECL(vm_reclaim_copyio_buffer_error, "Force a copyio error on the buffer",
538*4f1223e8SApple OSS Distributions T_META_IGNORECRASHES(".*deallocate_buffer.*"),
539*4f1223e8SApple OSS Distributions T_META_SYSCTL_INT(VM_RECLAIM_THRESHOLD_SYSCTL_HIGH),
540*4f1223e8SApple OSS Distributions T_META_TAG_VM_PREFERRED)
541*4f1223e8SApple OSS Distributions {
542*4f1223e8SApple OSS Distributions int status = spawn_helper_and_wait_for_exit("deallocate_buffer");
543*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(status), "Test process crashed.");
544*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(WTERMSIG(status), SIGKILL, "Test process crashed with SIGKILL.");
545*4f1223e8SApple OSS Distributions }
546*4f1223e8SApple OSS Distributions
547*4f1223e8SApple OSS Distributions T_HELPER_DECL(dealloc_gap, "Put a bad entry in the buffer")
548*4f1223e8SApple OSS Distributions {
549*4f1223e8SApple OSS Distributions struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
550*4f1223e8SApple OSS Distributions static const size_t kAllocationSize = (1UL << 20); // 1MB
551*4f1223e8SApple OSS Distributions mach_vm_address_t addr;
552*4f1223e8SApple OSS Distributions bool should_update_kernel_accounting = false;
553*4f1223e8SApple OSS Distributions
554*4f1223e8SApple OSS Distributions kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
555*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
556*4f1223e8SApple OSS Distributions
557*4f1223e8SApple OSS Distributions uint64_t idx = allocate_and_defer_deallocate(kAllocationSize, &ringbuffer, 1, &addr);
558*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(idx, 0ULL, "Entry placed at start of buffer");
559*4f1223e8SApple OSS Distributions idx = mach_vm_reclaim_mark_free(&ringbuffer, addr, (uint32_t) kAllocationSize, MACH_VM_RECLAIM_DEALLOCATE, &should_update_kernel_accounting);
560*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(idx, 1ULL, "Entry placed at correct index");
561*4f1223e8SApple OSS Distributions
562*4f1223e8SApple OSS Distributions mach_vm_reclaim_synchronize(&ringbuffer, 2);
563*4f1223e8SApple OSS Distributions
564*4f1223e8SApple OSS Distributions T_FAIL("Test did not crash when doing a double free!");
565*4f1223e8SApple OSS Distributions }
566*4f1223e8SApple OSS Distributions
567*4f1223e8SApple OSS Distributions T_DECL(vm_reclaim_dealloc_gap, "Ensure a dealloc gap delivers a fatal exception",
568*4f1223e8SApple OSS Distributions T_META_IGNORECRASHES(".*vm_reclaim_dealloc_gap.*"),
569*4f1223e8SApple OSS Distributions T_META_SYSCTL_INT(VM_RECLAIM_THRESHOLD_SYSCTL_LOW),
570*4f1223e8SApple OSS Distributions T_META_TAG_VM_PREFERRED)
571*4f1223e8SApple OSS Distributions {
572*4f1223e8SApple OSS Distributions int status = spawn_helper_and_wait_for_exit("dealloc_gap");
573*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(status), "Test process crashed.");
574*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(WTERMSIG(status), SIGKILL, "Test process crashed with SIGKILL.");
575*4f1223e8SApple OSS Distributions }
576*4f1223e8SApple OSS Distributions
577*4f1223e8SApple OSS Distributions T_HELPER_DECL(allocate_and_suspend_with_dealloc_gap,
578*4f1223e8SApple OSS Distributions "defer double free, and signal parent to suspend")
579*4f1223e8SApple OSS Distributions {
580*4f1223e8SApple OSS Distributions allocate_and_suspend(argv, false, true);
581*4f1223e8SApple OSS Distributions }
582*4f1223e8SApple OSS Distributions
583*4f1223e8SApple OSS Distributions static void
vm_reclaim_async_exception(char * variant,char * arg1)584*4f1223e8SApple OSS Distributions vm_reclaim_async_exception(char *variant, char *arg1)
585*4f1223e8SApple OSS Distributions {
586*4f1223e8SApple OSS Distributions test_after_background_helper_launches(variant, arg1, ^{
587*4f1223e8SApple OSS Distributions int ret = 0;
588*4f1223e8SApple OSS Distributions ret = pid_suspend(child_pid);
589*4f1223e8SApple OSS Distributions T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
590*4f1223e8SApple OSS Distributions /*
591*4f1223e8SApple OSS Distributions * The reclaim work is kicked off asynchronously by the suspend.
592*4f1223e8SApple OSS Distributions * So we need to call into the kernel to synchronize with the reclaim worker
593*4f1223e8SApple OSS Distributions * thread.
594*4f1223e8SApple OSS Distributions */
595*4f1223e8SApple OSS Distributions drain_async_queue(child_pid);
596*4f1223e8SApple OSS Distributions }, ^{
597*4f1223e8SApple OSS Distributions int status;
598*4f1223e8SApple OSS Distributions pid_t rc = waitpid(child_pid, &status, 0);
599*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
600*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(status), "Test process crashed.");
601*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(WTERMSIG(status), SIGKILL, "Test process crashed with SIGKILL.");
602*4f1223e8SApple OSS Distributions T_END;
603*4f1223e8SApple OSS Distributions });
604*4f1223e8SApple OSS Distributions }
605*4f1223e8SApple OSS Distributions
606*4f1223e8SApple OSS Distributions T_DECL(vm_reclaim_dealloc_gap_async, "Ensure a dealloc gap delivers an async fatal exception",
607*4f1223e8SApple OSS Distributions T_META_IGNORECRASHES(".*vm_reclaim_dealloc_gap_async.*"),
608*4f1223e8SApple OSS Distributions T_META_SYSCTL_INT(VM_RECLAIM_THRESHOLD_SYSCTL_LOW),
609*4f1223e8SApple OSS Distributions T_META_TAG_VM_PREFERRED)
610*4f1223e8SApple OSS Distributions {
611*4f1223e8SApple OSS Distributions vm_reclaim_async_exception("allocate_and_suspend_with_dealloc_gap", "15");
612*4f1223e8SApple OSS Distributions }
613*4f1223e8SApple OSS Distributions
614*4f1223e8SApple OSS Distributions T_HELPER_DECL(allocate_and_suspend_with_buffer_error,
615*4f1223e8SApple OSS Distributions "defer free, free buffer, and signal parent to suspend")
616*4f1223e8SApple OSS Distributions {
617*4f1223e8SApple OSS Distributions allocate_and_suspend(argv, true, false);
618*4f1223e8SApple OSS Distributions }
619*4f1223e8SApple OSS Distributions
620*4f1223e8SApple OSS Distributions T_DECL(vm_reclaim_copyio_buffer_error_async, "Ensure a buffer copyio failure delivers an async fatal exception",
621*4f1223e8SApple OSS Distributions T_META_IGNORECRASHES(".*vm_reclaim_dealloc_gap_async.*"),
622*4f1223e8SApple OSS Distributions T_META_SYSCTL_INT(VM_RECLAIM_THRESHOLD_SYSCTL_HIGH),
623*4f1223e8SApple OSS Distributions T_META_TAG_VM_PREFERRED)
624*4f1223e8SApple OSS Distributions {
625*4f1223e8SApple OSS Distributions vm_reclaim_async_exception("allocate_and_suspend_with_buffer_error", "15");
626*4f1223e8SApple OSS Distributions }
627*4f1223e8SApple OSS Distributions
628*4f1223e8SApple OSS Distributions T_HELPER_DECL(reuse_freed_entry_fork,
629*4f1223e8SApple OSS Distributions "defer free, sync, and try to use entry")
630*4f1223e8SApple OSS Distributions {
631*4f1223e8SApple OSS Distributions struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
632*4f1223e8SApple OSS Distributions static const size_t kAllocationSize = (1UL << 20); // 1MB
633*4f1223e8SApple OSS Distributions mach_vm_address_t addr;
634*4f1223e8SApple OSS Distributions static const unsigned char kValue = 119;
635*4f1223e8SApple OSS Distributions
636*4f1223e8SApple OSS Distributions kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
637*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
638*4f1223e8SApple OSS Distributions
639*4f1223e8SApple OSS Distributions uint64_t idx = allocate_and_defer_deallocate(kAllocationSize, &ringbuffer, kValue, &addr);
640*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(idx, 0ULL, "Entry placed at start of buffer");
641*4f1223e8SApple OSS Distributions
642*4f1223e8SApple OSS Distributions pid_t forked_pid = fork();
643*4f1223e8SApple OSS Distributions T_QUIET; T_WITH_ERRNO; T_ASSERT_NE(forked_pid, -1, "fork()");
644*4f1223e8SApple OSS Distributions if (forked_pid == 0) {
645*4f1223e8SApple OSS Distributions kr = mach_vm_reclaim_synchronize(&ringbuffer, 10);
646*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_synchronize");
647*4f1223e8SApple OSS Distributions assert_buffer_has_changed_and_crash(addr, kAllocationSize, kValue);
648*4f1223e8SApple OSS Distributions } else {
649*4f1223e8SApple OSS Distributions int status;
650*4f1223e8SApple OSS Distributions pid_t rc = waitpid(forked_pid, &status, 0);
651*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(rc, forked_pid, "waitpid");
652*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(status), "Forked process crashed.");
653*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(WTERMSIG(status), SIGSEGV, "Forked process crashed with segmentation fault.");
654*4f1223e8SApple OSS Distributions }
655*4f1223e8SApple OSS Distributions }
656*4f1223e8SApple OSS Distributions
657*4f1223e8SApple OSS Distributions T_DECL(vm_reclaim_fork, "Ensure reclaim buffer is inherited across a fork",
658*4f1223e8SApple OSS Distributions T_META_IGNORECRASHES(".*vm_reclaim_fork.*"),
659*4f1223e8SApple OSS Distributions T_META_SYSCTL_INT(VM_RECLAIM_THRESHOLD_SYSCTL_HIGH),
660*4f1223e8SApple OSS Distributions T_META_TAG_VM_PREFERRED)
661*4f1223e8SApple OSS Distributions {
662*4f1223e8SApple OSS Distributions int status = spawn_helper_and_wait_for_exit("reuse_freed_entry_fork");
663*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_TRUE(WIFEXITED(status), "Test process exited.");
664*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(WEXITSTATUS(status), 0, "Test process exited cleanly.");
665*4f1223e8SApple OSS Distributions }
666*4f1223e8SApple OSS Distributions
667*4f1223e8SApple OSS Distributions #define SUSPEND_AND_RESUME_COUNT 4
668*4f1223e8SApple OSS Distributions
669*4f1223e8SApple OSS Distributions // rdar://110081398
670*4f1223e8SApple OSS Distributions T_DECL(reclaim_async_on_repeated_suspend,
671*4f1223e8SApple OSS Distributions "verify that subsequent suspends are allowed",
672*4f1223e8SApple OSS Distributions T_META_SYSCTL_INT(VM_RECLAIM_THRESHOLD_SYSCTL_HIGH),
673*4f1223e8SApple OSS Distributions T_META_TAG_VM_PREFERRED)
674*4f1223e8SApple OSS Distributions {
675*4f1223e8SApple OSS Distributions const int sleep_duration = 3;
676*4f1223e8SApple OSS Distributions test_after_background_helper_launches("allocate_and_suspend", "20", ^{
677*4f1223e8SApple OSS Distributions int ret = 0;
678*4f1223e8SApple OSS Distributions for (int i = 0; i < SUSPEND_AND_RESUME_COUNT; i++) {
679*4f1223e8SApple OSS Distributions ret = pid_suspend(child_pid);
680*4f1223e8SApple OSS Distributions T_ASSERT_POSIX_SUCCESS(ret, "pid_suspend()");
681*4f1223e8SApple OSS Distributions ret = pid_resume(child_pid);
682*4f1223e8SApple OSS Distributions T_ASSERT_POSIX_SUCCESS(ret, "pid_resume()");
683*4f1223e8SApple OSS Distributions }
684*4f1223e8SApple OSS Distributions T_LOG("Sleeping %d sec...", sleep_duration);
685*4f1223e8SApple OSS Distributions sleep(sleep_duration);
686*4f1223e8SApple OSS Distributions T_LOG("Killing child...");
687*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "kill()");
688*4f1223e8SApple OSS Distributions }, ^{
689*4f1223e8SApple OSS Distributions int status;
690*4f1223e8SApple OSS Distributions pid_t rc = waitpid(child_pid, &status, 0);
691*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
692*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_EQ(WEXITSTATUS(status), 0, "Test process exited cleanly.");
693*4f1223e8SApple OSS Distributions T_END;
694*4f1223e8SApple OSS Distributions });
695*4f1223e8SApple OSS Distributions }
696*4f1223e8SApple OSS Distributions
697*4f1223e8SApple OSS Distributions T_HELPER_DECL(ringbuffer_init_after_exec,
698*4f1223e8SApple OSS Distributions "initialize a ringbuffer after exec")
699*4f1223e8SApple OSS Distributions {
700*4f1223e8SApple OSS Distributions struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
701*4f1223e8SApple OSS Distributions kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
702*4f1223e8SApple OSS Distributions T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init()");
703*4f1223e8SApple OSS Distributions }
704*4f1223e8SApple OSS Distributions
705*4f1223e8SApple OSS Distributions extern char **environ;
706*4f1223e8SApple OSS Distributions
707*4f1223e8SApple OSS Distributions T_HELPER_DECL(exec_after_ringbuffer_init,
708*4f1223e8SApple OSS Distributions "initialize a ringbuffer then exec")
709*4f1223e8SApple OSS Distributions {
710*4f1223e8SApple OSS Distributions char **launch_tool_args;
711*4f1223e8SApple OSS Distributions char testpath[PATH_MAX];
712*4f1223e8SApple OSS Distributions uint32_t testpath_buf_size;
713*4f1223e8SApple OSS Distributions struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
714*4f1223e8SApple OSS Distributions
715*4f1223e8SApple OSS Distributions kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
716*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init()");
717*4f1223e8SApple OSS Distributions
718*4f1223e8SApple OSS Distributions testpath_buf_size = sizeof(testpath);
719*4f1223e8SApple OSS Distributions int ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
720*4f1223e8SApple OSS Distributions T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
721*4f1223e8SApple OSS Distributions T_LOG("Executable path: %s", testpath);
722*4f1223e8SApple OSS Distributions launch_tool_args = (char *[]){
723*4f1223e8SApple OSS Distributions testpath,
724*4f1223e8SApple OSS Distributions "-n",
725*4f1223e8SApple OSS Distributions "ringbuffer_init_after_exec",
726*4f1223e8SApple OSS Distributions NULL
727*4f1223e8SApple OSS Distributions };
728*4f1223e8SApple OSS Distributions
729*4f1223e8SApple OSS Distributions /* Spawn the child process. */
730*4f1223e8SApple OSS Distributions posix_spawnattr_t spawnattrs;
731*4f1223e8SApple OSS Distributions posix_spawnattr_init(&spawnattrs);
732*4f1223e8SApple OSS Distributions posix_spawnattr_setflags(&spawnattrs, POSIX_SPAWN_SETEXEC);
733*4f1223e8SApple OSS Distributions posix_spawn(&child_pid, testpath, NULL, &spawnattrs, launch_tool_args, environ);
734*4f1223e8SApple OSS Distributions T_ASSERT_FAIL("should not be reached");
735*4f1223e8SApple OSS Distributions }
736*4f1223e8SApple OSS Distributions
737*4f1223e8SApple OSS Distributions T_DECL(reclaim_exec_new_reclaim_buffer,
738*4f1223e8SApple OSS Distributions "verify that an exec-ed process may instantiate a new buffer",
739*4f1223e8SApple OSS Distributions T_META_SYSCTL_INT(VM_RECLAIM_THRESHOLD_SYSCTL_LOW),
740*4f1223e8SApple OSS Distributions T_META_TAG_VM_PREFERRED)
741*4f1223e8SApple OSS Distributions {
742*4f1223e8SApple OSS Distributions dt_helper_t helpers[1];
743*4f1223e8SApple OSS Distributions helpers[0] = dt_child_helper("exec_after_ringbuffer_init");
744*4f1223e8SApple OSS Distributions
745*4f1223e8SApple OSS Distributions dt_run_helpers(helpers, 1, 30);
746*4f1223e8SApple OSS Distributions }
747