xref: /xnu-8796.101.5/tests/vm/vm_reclaim.c (revision aca3beaa3dfbd42498b42c5e5ce20a938e6554e5)
1*aca3beaaSApple OSS Distributions #include <sys/types.h>
2*aca3beaaSApple OSS Distributions #include <sys/sysctl.h>
3*aca3beaaSApple OSS Distributions #include <mach/mach.h>
4*aca3beaaSApple OSS Distributions #include <mach/mach_vm.h>
5*aca3beaaSApple OSS Distributions #include <mach/vm_reclaim.h>
6*aca3beaaSApple OSS Distributions #include <mach-o/dyld.h>
7*aca3beaaSApple OSS Distributions #include <os/atomic_private.h>
8*aca3beaaSApple OSS Distributions #include <signal.h>
9*aca3beaaSApple OSS Distributions 
10*aca3beaaSApple OSS Distributions #include <darwintest.h>
11*aca3beaaSApple OSS Distributions #include <darwintest_utils.h>
12*aca3beaaSApple OSS Distributions 
13*aca3beaaSApple OSS Distributions #include <Kernel/kern/ledger.h>
14*aca3beaaSApple OSS Distributions extern int ledger(int cmd, caddr_t arg1, caddr_t arg2, caddr_t arg3);
15*aca3beaaSApple OSS Distributions 
16*aca3beaaSApple OSS Distributions #include "memorystatus_assertion_helpers.h"
17*aca3beaaSApple OSS Distributions 
18*aca3beaaSApple OSS Distributions // Some of the unit tests test deferred deallocations.
19*aca3beaaSApple OSS Distributions // For these we need to set a sufficiently large reclaim threshold
20*aca3beaaSApple OSS Distributions // to ensure their buffers aren't freed prematurely.
21*aca3beaaSApple OSS Distributions #define VM_RECLAIM_THRESHOLD_BOOT_ARG "vm_reclaim_max_threshold=268435456"
22*aca3beaaSApple OSS Distributions 
23*aca3beaaSApple OSS Distributions T_GLOBAL_META(
24*aca3beaaSApple OSS Distributions 	T_META_NAMESPACE("xnu.vm"),
25*aca3beaaSApple OSS Distributions 	T_META_RADAR_COMPONENT_NAME("xnu"),
26*aca3beaaSApple OSS Distributions 	T_META_RADAR_COMPONENT_VERSION("VM"),
27*aca3beaaSApple OSS Distributions 	T_META_ENABLED(TARGET_OS_IOS && !TARGET_OS_MACCATALYST),
28*aca3beaaSApple OSS Distributions 	T_META_ENVVAR("MallocLargeCache=0") // Ensure we don't conflict with libmalloc's reclaim buffer
29*aca3beaaSApple OSS Distributions 	);
30*aca3beaaSApple OSS Distributions 
31*aca3beaaSApple OSS Distributions T_DECL(vm_reclaim_init, "Set up and tear down a reclaim buffer")
32*aca3beaaSApple OSS Distributions {
33*aca3beaaSApple OSS Distributions 	struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
34*aca3beaaSApple OSS Distributions 
35*aca3beaaSApple OSS Distributions 	kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
36*aca3beaaSApple OSS Distributions 
37*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
38*aca3beaaSApple OSS Distributions }
39*aca3beaaSApple OSS Distributions 
40*aca3beaaSApple OSS Distributions /*
41*aca3beaaSApple OSS Distributions  * Allocate a buffer of the given size, write val to each byte, and free it via a deferred free call.
42*aca3beaaSApple OSS Distributions  */
43*aca3beaaSApple OSS Distributions static uint64_t
allocate_and_defer_free(size_t size,mach_vm_reclaim_ringbuffer_v1_t ringbuffer,unsigned char val,mach_vm_address_t * addr)44*aca3beaaSApple OSS Distributions allocate_and_defer_free(size_t size, mach_vm_reclaim_ringbuffer_v1_t ringbuffer, unsigned char val, mach_vm_address_t *addr /* OUT */)
45*aca3beaaSApple OSS Distributions {
46*aca3beaaSApple 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);
47*aca3beaaSApple OSS Distributions 	bool should_update_kernel_accounting = false;
48*aca3beaaSApple OSS Distributions 	uint64_t idx;
49*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_map");
50*aca3beaaSApple OSS Distributions 
51*aca3beaaSApple OSS Distributions 	memset((void *) *addr, val, size);
52*aca3beaaSApple OSS Distributions 
53*aca3beaaSApple OSS Distributions 	idx = mach_vm_reclaim_mark_free(ringbuffer, *addr, (uint32_t) size, &should_update_kernel_accounting);
54*aca3beaaSApple OSS Distributions 	if (should_update_kernel_accounting) {
55*aca3beaaSApple OSS Distributions 		mach_vm_reclaim_update_kernel_accounting(ringbuffer);
56*aca3beaaSApple OSS Distributions 	}
57*aca3beaaSApple OSS Distributions 	return idx;
58*aca3beaaSApple OSS Distributions }
59*aca3beaaSApple OSS Distributions 
60*aca3beaaSApple OSS Distributions T_DECL(vm_reclaim_single_entry, "Place a single entry in the buffer and call sync")
61*aca3beaaSApple OSS Distributions {
62*aca3beaaSApple OSS Distributions 	struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
63*aca3beaaSApple OSS Distributions 	static const size_t kAllocationSize = (1UL << 20); // 1MB
64*aca3beaaSApple OSS Distributions 	mach_vm_address_t addr;
65*aca3beaaSApple OSS Distributions 
66*aca3beaaSApple OSS Distributions 	kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
67*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
68*aca3beaaSApple OSS Distributions 
69*aca3beaaSApple OSS Distributions 	uint64_t idx = allocate_and_defer_free(kAllocationSize, &ringbuffer, 1, &addr);
70*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_EQ(idx, 0ULL, "Entry placed at start of buffer");
71*aca3beaaSApple OSS Distributions 	mach_vm_reclaim_synchronize(&ringbuffer, 1);
72*aca3beaaSApple OSS Distributions }
73*aca3beaaSApple OSS Distributions 
74*aca3beaaSApple OSS Distributions static int
spawn_helper_and_wait_for_exit(char * helper)75*aca3beaaSApple OSS Distributions spawn_helper_and_wait_for_exit(char *helper)
76*aca3beaaSApple OSS Distributions {
77*aca3beaaSApple OSS Distributions 	char **launch_tool_args;
78*aca3beaaSApple OSS Distributions 	char testpath[PATH_MAX];
79*aca3beaaSApple OSS Distributions 	uint32_t testpath_buf_size;
80*aca3beaaSApple OSS Distributions 	pid_t child_pid;
81*aca3beaaSApple OSS Distributions 
82*aca3beaaSApple OSS Distributions 	testpath_buf_size = sizeof(testpath);
83*aca3beaaSApple OSS Distributions 	int ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
84*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
85*aca3beaaSApple OSS Distributions 	T_LOG("Executable path: %s", testpath);
86*aca3beaaSApple OSS Distributions 	launch_tool_args = (char *[]){
87*aca3beaaSApple OSS Distributions 		testpath,
88*aca3beaaSApple OSS Distributions 		"-n",
89*aca3beaaSApple OSS Distributions 		helper,
90*aca3beaaSApple OSS Distributions 		NULL
91*aca3beaaSApple OSS Distributions 	};
92*aca3beaaSApple OSS Distributions 
93*aca3beaaSApple OSS Distributions 	/* Spawn the child process. */
94*aca3beaaSApple OSS Distributions 	ret = dt_launch_tool(&child_pid, launch_tool_args, false, NULL, NULL);
95*aca3beaaSApple OSS Distributions 	if (ret != 0) {
96*aca3beaaSApple OSS Distributions 		T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
97*aca3beaaSApple OSS Distributions 	}
98*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_POSIX_SUCCESS(child_pid, "dt_launch_tool");
99*aca3beaaSApple OSS Distributions 
100*aca3beaaSApple OSS Distributions 	int status;
101*aca3beaaSApple OSS Distributions 	pid_t rc = waitpid(child_pid, &status, 0);
102*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
103*aca3beaaSApple OSS Distributions 	return status;
104*aca3beaaSApple OSS Distributions }
105*aca3beaaSApple OSS Distributions 
106*aca3beaaSApple OSS Distributions /*
107*aca3beaaSApple OSS Distributions  * Returns true iff every entry in buffer is expected.
108*aca3beaaSApple OSS Distributions  */
109*aca3beaaSApple OSS Distributions static bool
check_buffer(mach_vm_address_t addr,size_t size,unsigned char expected)110*aca3beaaSApple OSS Distributions check_buffer(mach_vm_address_t addr, size_t size, unsigned char expected)
111*aca3beaaSApple OSS Distributions {
112*aca3beaaSApple OSS Distributions 	unsigned char *buffer = (unsigned char *) addr;
113*aca3beaaSApple OSS Distributions 	for (size_t i = 0; i < size; i++) {
114*aca3beaaSApple OSS Distributions 		if (buffer[i] != expected) {
115*aca3beaaSApple OSS Distributions 			return false;
116*aca3beaaSApple OSS Distributions 		}
117*aca3beaaSApple OSS Distributions 	}
118*aca3beaaSApple OSS Distributions 	return true;
119*aca3beaaSApple OSS Distributions }
120*aca3beaaSApple OSS Distributions 
121*aca3beaaSApple OSS Distributions /*
122*aca3beaaSApple OSS Distributions  * Check that the given (freed) buffer has changed.
123*aca3beaaSApple OSS Distributions  * This will likely crash, but if we make it through the entire buffer then segfault on purpose.
124*aca3beaaSApple OSS Distributions  */
125*aca3beaaSApple OSS Distributions static void
assert_buffer_has_changed_and_crash(mach_vm_address_t addr,size_t size,unsigned char expected)126*aca3beaaSApple OSS Distributions assert_buffer_has_changed_and_crash(mach_vm_address_t addr, size_t size, unsigned char expected)
127*aca3beaaSApple OSS Distributions {
128*aca3beaaSApple OSS Distributions 	/*
129*aca3beaaSApple OSS Distributions 	 * mach_vm_reclaim_synchronize should have ensured the buffer was freed.
130*aca3beaaSApple OSS Distributions 	 * Two cases:
131*aca3beaaSApple OSS Distributions 	 * 1. The buffer is still still free (touching it causes a crash)
132*aca3beaaSApple OSS Distributions 	 * 2. The address range was re-allocated by some other library in process.
133*aca3beaaSApple OSS Distributions 	 * #1 is far more likely. But if #2 happened, the buffer shouldn't be filled
134*aca3beaaSApple OSS Distributions 	 * with the value we wrote to it. So scan the buffer. If we segfault it's case #1
135*aca3beaaSApple OSS Distributions 	 * and if we see another value it's case #2.
136*aca3beaaSApple OSS Distributions 	 */
137*aca3beaaSApple OSS Distributions 	bool changed = !check_buffer(addr, size, expected);
138*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_TRUE(changed, "buffer was re-allocated");
139*aca3beaaSApple OSS Distributions 	/* Case #2. Force a segfault so the parent sees that we crashed. */
140*aca3beaaSApple OSS Distributions 	*(volatile int *) 0 = 1;
141*aca3beaaSApple OSS Distributions 
142*aca3beaaSApple OSS Distributions 	T_FAIL("Test did not crash when dereferencing NULL");
143*aca3beaaSApple OSS Distributions }
144*aca3beaaSApple OSS Distributions 
145*aca3beaaSApple OSS Distributions T_HELPER_DECL(reuse_freed_entry,
146*aca3beaaSApple OSS Distributions     "defer free, sync, and try to use entry")
147*aca3beaaSApple OSS Distributions {
148*aca3beaaSApple OSS Distributions 	struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
149*aca3beaaSApple OSS Distributions 	static const size_t kAllocationSize = (1UL << 20); // 1MB
150*aca3beaaSApple OSS Distributions 	mach_vm_address_t addr;
151*aca3beaaSApple OSS Distributions 	static const unsigned char kValue = 220;
152*aca3beaaSApple OSS Distributions 
153*aca3beaaSApple OSS Distributions 	kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
154*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
155*aca3beaaSApple OSS Distributions 
156*aca3beaaSApple OSS Distributions 	uint64_t idx = allocate_and_defer_free(kAllocationSize, &ringbuffer, kValue, &addr);
157*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_EQ(idx, 0ULL, "Entry placed at start of buffer");
158*aca3beaaSApple OSS Distributions 	kr = mach_vm_reclaim_synchronize(&ringbuffer, 10);
159*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_synchronize");
160*aca3beaaSApple OSS Distributions 	assert_buffer_has_changed_and_crash(addr, kAllocationSize, kValue);
161*aca3beaaSApple OSS Distributions }
162*aca3beaaSApple OSS Distributions 
163*aca3beaaSApple OSS Distributions T_DECL(vm_reclaim_single_entry_verify_free, "Place a single entry in the buffer and call sync",
164*aca3beaaSApple OSS Distributions     T_META_IGNORECRASHES("vm_reclaim_single_entry_verify_free*"))
165*aca3beaaSApple OSS Distributions {
166*aca3beaaSApple OSS Distributions 	int status = spawn_helper_and_wait_for_exit("reuse_freed_entry");
167*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(status), "Test process crashed.");
168*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_EQ(WTERMSIG(status), SIGSEGV, "Test process crashed with segmentation fault.");
169*aca3beaaSApple OSS Distributions }
170*aca3beaaSApple OSS Distributions 
171*aca3beaaSApple OSS Distributions static void
allocate_and_suspend(char * const * argv,bool free_buffer,bool double_free)172*aca3beaaSApple OSS Distributions allocate_and_suspend(char *const *argv, bool free_buffer, bool double_free)
173*aca3beaaSApple OSS Distributions {
174*aca3beaaSApple OSS Distributions 	struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
175*aca3beaaSApple OSS Distributions 	static const size_t kAllocationSize = (1UL << 20); // 1MB
176*aca3beaaSApple OSS Distributions 	mach_vm_address_t addr = 0;
177*aca3beaaSApple OSS Distributions 	bool should_update_kernel_accounting = false;
178*aca3beaaSApple OSS Distributions 
179*aca3beaaSApple OSS Distributions 	const mach_vm_size_t kNumEntries = (size_t) atoi(argv[0]);
180*aca3beaaSApple OSS Distributions 
181*aca3beaaSApple OSS Distributions 	kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
182*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
183*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_LT(kNumEntries, ringbuffer.buffer_len, "Test does not fill up ringubffer");
184*aca3beaaSApple OSS Distributions 
185*aca3beaaSApple OSS Distributions 	for (size_t i = 0; i < kNumEntries; i++) {
186*aca3beaaSApple OSS Distributions 		uint64_t idx = allocate_and_defer_free(kAllocationSize, &ringbuffer, (unsigned char) i, &addr);
187*aca3beaaSApple OSS Distributions 		T_QUIET; T_ASSERT_EQ(idx, (uint64_t) i, "idx is correct");
188*aca3beaaSApple OSS Distributions 	}
189*aca3beaaSApple OSS Distributions 
190*aca3beaaSApple OSS Distributions 	if (double_free) {
191*aca3beaaSApple OSS Distributions 		// Double free the last entry
192*aca3beaaSApple OSS Distributions 		mach_vm_reclaim_mark_free(&ringbuffer, addr, (uint32_t) kAllocationSize, &should_update_kernel_accounting);
193*aca3beaaSApple OSS Distributions 		T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_mark_free");
194*aca3beaaSApple OSS Distributions 	}
195*aca3beaaSApple OSS Distributions 
196*aca3beaaSApple OSS Distributions 	if (free_buffer) {
197*aca3beaaSApple OSS Distributions 		kr = mach_vm_deallocate(mach_task_self(), (mach_vm_address_t) ringbuffer.buffer, ringbuffer.buffer_len * sizeof(mach_vm_reclaim_entry_v1_t));
198*aca3beaaSApple OSS Distributions 		T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_deallocate");
199*aca3beaaSApple OSS Distributions 	}
200*aca3beaaSApple OSS Distributions 
201*aca3beaaSApple OSS Distributions 	// Signal to our parent to suspend us
202*aca3beaaSApple OSS Distributions 	if (kill(getppid(), SIGUSR1) != 0) {
203*aca3beaaSApple OSS Distributions 		T_LOG("Unable to signal to parent process!");
204*aca3beaaSApple OSS Distributions 		exit(1);
205*aca3beaaSApple OSS Distributions 	}
206*aca3beaaSApple OSS Distributions 
207*aca3beaaSApple OSS Distributions 	while (1) {
208*aca3beaaSApple OSS Distributions 		;
209*aca3beaaSApple OSS Distributions 	}
210*aca3beaaSApple OSS Distributions }
211*aca3beaaSApple OSS Distributions 
212*aca3beaaSApple OSS Distributions T_HELPER_DECL(allocate_and_suspend,
213*aca3beaaSApple OSS Distributions     "defer free, and signal parent to suspend")
214*aca3beaaSApple OSS Distributions {
215*aca3beaaSApple OSS Distributions 	allocate_and_suspend(argv, false, false);
216*aca3beaaSApple OSS Distributions }
217*aca3beaaSApple OSS Distributions 
218*aca3beaaSApple OSS Distributions static void
resume_and_kill_proc(pid_t pid)219*aca3beaaSApple OSS Distributions resume_and_kill_proc(pid_t pid)
220*aca3beaaSApple OSS Distributions {
221*aca3beaaSApple OSS Distributions 	int ret = pid_resume(pid);
222*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "proc resumed after freeze");
223*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(pid, SIGKILL), "Killed process");
224*aca3beaaSApple OSS Distributions }
225*aca3beaaSApple OSS Distributions 
226*aca3beaaSApple OSS Distributions static void
drain_async_queue(pid_t child_pid)227*aca3beaaSApple OSS Distributions drain_async_queue(pid_t child_pid)
228*aca3beaaSApple OSS Distributions {
229*aca3beaaSApple OSS Distributions 	int val = child_pid;
230*aca3beaaSApple OSS Distributions 	int ret;
231*aca3beaaSApple OSS Distributions 	size_t len = sizeof(val);
232*aca3beaaSApple OSS Distributions 	ret = sysctlbyname("vm.reclaim_drain_async_queue", NULL, NULL, &val, len);
233*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "vm.reclaim_drain_async_queue");
234*aca3beaaSApple OSS Distributions }
235*aca3beaaSApple OSS Distributions 
236*aca3beaaSApple OSS Distributions static size_t
ledger_phys_footprint_index(size_t * num_entries)237*aca3beaaSApple OSS Distributions ledger_phys_footprint_index(size_t *num_entries)
238*aca3beaaSApple OSS Distributions {
239*aca3beaaSApple OSS Distributions 	struct ledger_info li;
240*aca3beaaSApple OSS Distributions 	struct ledger_template_info *templateInfo = NULL;
241*aca3beaaSApple OSS Distributions 	int ret;
242*aca3beaaSApple OSS Distributions 	size_t i, footprint_index;
243*aca3beaaSApple OSS Distributions 	bool found = false;
244*aca3beaaSApple OSS Distributions 
245*aca3beaaSApple OSS Distributions 	ret = ledger(LEDGER_INFO, (caddr_t)(uintptr_t)getpid(), (caddr_t)&li, NULL);
246*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "ledger(LEDGER_INFO)");
247*aca3beaaSApple OSS Distributions 
248*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_GT(li.li_entries, (int64_t) 0, "num ledger entries is valid");
249*aca3beaaSApple OSS Distributions 	*num_entries = (size_t) li.li_entries;
250*aca3beaaSApple OSS Distributions 	templateInfo = malloc((size_t)li.li_entries * sizeof(struct ledger_template_info));
251*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_NOTNULL(templateInfo, "malloc entries");
252*aca3beaaSApple OSS Distributions 
253*aca3beaaSApple OSS Distributions 	footprint_index = 0;
254*aca3beaaSApple OSS Distributions 	ret = ledger(LEDGER_TEMPLATE_INFO, (caddr_t) templateInfo, (caddr_t) num_entries, NULL);
255*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "ledger(LEDGER_TEMPLATE_INFO)");
256*aca3beaaSApple OSS Distributions 	for (i = 0; i < *num_entries; i++) {
257*aca3beaaSApple OSS Distributions 		if (strcmp(templateInfo[i].lti_name, "phys_footprint") == 0) {
258*aca3beaaSApple OSS Distributions 			footprint_index = i;
259*aca3beaaSApple OSS Distributions 			found = true;
260*aca3beaaSApple OSS Distributions 		}
261*aca3beaaSApple OSS Distributions 	}
262*aca3beaaSApple OSS Distributions 	free(templateInfo);
263*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_TRUE(found, "found phys_footprint in ledger");
264*aca3beaaSApple OSS Distributions 	return footprint_index;
265*aca3beaaSApple OSS Distributions }
266*aca3beaaSApple OSS Distributions 
267*aca3beaaSApple OSS Distributions static int64_t
get_ledger_entry_for_pid(pid_t pid,size_t index,size_t num_entries)268*aca3beaaSApple OSS Distributions get_ledger_entry_for_pid(pid_t pid, size_t index, size_t num_entries)
269*aca3beaaSApple OSS Distributions {
270*aca3beaaSApple OSS Distributions 	int ret;
271*aca3beaaSApple OSS Distributions 	int64_t value;
272*aca3beaaSApple OSS Distributions 	struct ledger_entry_info *lei = NULL;
273*aca3beaaSApple OSS Distributions 
274*aca3beaaSApple OSS Distributions 	lei = malloc(num_entries * sizeof(*lei));
275*aca3beaaSApple OSS Distributions 	ret = ledger(LEDGER_ENTRY_INFO, (caddr_t) (uintptr_t) pid, (caddr_t) lei, (caddr_t) &num_entries);
276*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "ledger(LEDGER_ENTRY_INFO)");
277*aca3beaaSApple OSS Distributions 	value = lei[index].lei_balance;
278*aca3beaaSApple OSS Distributions 	free(lei);
279*aca3beaaSApple OSS Distributions 	return value;
280*aca3beaaSApple OSS Distributions }
281*aca3beaaSApple OSS Distributions 
282*aca3beaaSApple OSS Distributions static pid_t child_pid;
283*aca3beaaSApple OSS Distributions 
284*aca3beaaSApple OSS Distributions static void
test_after_background_helper_launches(char * variant,char * arg1,dispatch_block_t test_block,dispatch_block_t exit_block)285*aca3beaaSApple OSS Distributions test_after_background_helper_launches(char* variant, char * arg1, dispatch_block_t test_block, dispatch_block_t exit_block)
286*aca3beaaSApple OSS Distributions {
287*aca3beaaSApple OSS Distributions 	char **launch_tool_args;
288*aca3beaaSApple OSS Distributions 	char testpath[PATH_MAX];
289*aca3beaaSApple OSS Distributions 	uint32_t testpath_buf_size;
290*aca3beaaSApple OSS Distributions 
291*aca3beaaSApple OSS Distributions 	dispatch_source_t ds_signal, ds_exit;
292*aca3beaaSApple OSS Distributions 
293*aca3beaaSApple OSS Distributions 	/* Wait for the child process to tell us that it's ready, and then freeze it */
294*aca3beaaSApple OSS Distributions 	signal(SIGUSR1, SIG_IGN);
295*aca3beaaSApple OSS Distributions 	ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
296*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create");
297*aca3beaaSApple OSS Distributions 	dispatch_source_set_event_handler(ds_signal, test_block);
298*aca3beaaSApple OSS Distributions 
299*aca3beaaSApple OSS Distributions 	dispatch_activate(ds_signal);
300*aca3beaaSApple OSS Distributions 
301*aca3beaaSApple OSS Distributions 	testpath_buf_size = sizeof(testpath);
302*aca3beaaSApple OSS Distributions 	int ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
303*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
304*aca3beaaSApple OSS Distributions 	T_LOG("Executable path: %s", testpath);
305*aca3beaaSApple OSS Distributions 	launch_tool_args = (char *[]){
306*aca3beaaSApple OSS Distributions 		testpath,
307*aca3beaaSApple OSS Distributions 		"-n",
308*aca3beaaSApple OSS Distributions 		variant,
309*aca3beaaSApple OSS Distributions 		arg1,
310*aca3beaaSApple OSS Distributions 		NULL
311*aca3beaaSApple OSS Distributions 	};
312*aca3beaaSApple OSS Distributions 
313*aca3beaaSApple OSS Distributions 	/* Spawn the child process. */
314*aca3beaaSApple OSS Distributions 	ret = dt_launch_tool(&child_pid, launch_tool_args, false, NULL, NULL);
315*aca3beaaSApple OSS Distributions 	if (ret != 0) {
316*aca3beaaSApple OSS Distributions 		T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
317*aca3beaaSApple OSS Distributions 	}
318*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_POSIX_SUCCESS(child_pid, "dt_launch_tool");
319*aca3beaaSApple OSS Distributions 
320*aca3beaaSApple OSS Distributions 	/* Listen for exit. */
321*aca3beaaSApple OSS Distributions 	ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
322*aca3beaaSApple OSS Distributions 	dispatch_source_set_event_handler(ds_exit, exit_block);
323*aca3beaaSApple OSS Distributions 
324*aca3beaaSApple OSS Distributions 	dispatch_activate(ds_exit);
325*aca3beaaSApple OSS Distributions 	dispatch_main();
326*aca3beaaSApple OSS Distributions }
327*aca3beaaSApple OSS Distributions 
328*aca3beaaSApple OSS Distributions T_DECL(vm_reclaim_full_reclaim_on_suspend, "Defer free memory and then suspend.",
329*aca3beaaSApple OSS Distributions     T_META_ASROOT(true),
330*aca3beaaSApple OSS Distributions     T_META_BOOTARGS_SET(VM_RECLAIM_THRESHOLD_BOOT_ARG))
331*aca3beaaSApple OSS Distributions {
332*aca3beaaSApple OSS Distributions 	test_after_background_helper_launches("allocate_and_suspend", "20", ^{
333*aca3beaaSApple OSS Distributions 		int ret = 0;
334*aca3beaaSApple OSS Distributions 		size_t num_ledger_entries = 0;
335*aca3beaaSApple OSS Distributions 		size_t phys_footprint_index = ledger_phys_footprint_index(&num_ledger_entries);
336*aca3beaaSApple OSS Distributions 		int64_t before_footprint, after_footprint, reclaimable_bytes = 20 * (1ULL << 20);
337*aca3beaaSApple OSS Distributions 		before_footprint = get_ledger_entry_for_pid(child_pid, phys_footprint_index, num_ledger_entries);
338*aca3beaaSApple OSS Distributions 		T_QUIET; T_EXPECT_GE(before_footprint, reclaimable_bytes, "memory was allocated");
339*aca3beaaSApple OSS Distributions 		ret = pid_suspend(child_pid);
340*aca3beaaSApple OSS Distributions 		T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
341*aca3beaaSApple OSS Distributions 		/*
342*aca3beaaSApple OSS Distributions 		 * The reclaim work is kicked off asynchronously by the suspend.
343*aca3beaaSApple OSS Distributions 		 * So we need to call into the kernel to synchronize with the reclaim worker
344*aca3beaaSApple OSS Distributions 		 * thread.
345*aca3beaaSApple OSS Distributions 		 */
346*aca3beaaSApple OSS Distributions 		drain_async_queue(child_pid);
347*aca3beaaSApple OSS Distributions 
348*aca3beaaSApple OSS Distributions 		after_footprint = get_ledger_entry_for_pid(child_pid, phys_footprint_index, num_ledger_entries);
349*aca3beaaSApple OSS Distributions 		T_QUIET; T_EXPECT_LE(after_footprint, before_footprint - reclaimable_bytes, "memory was reclaimed");
350*aca3beaaSApple OSS Distributions 
351*aca3beaaSApple OSS Distributions 		resume_and_kill_proc(child_pid);
352*aca3beaaSApple OSS Distributions 	},
353*aca3beaaSApple OSS Distributions 	    ^{
354*aca3beaaSApple OSS Distributions 		int status = 0, code = 0;
355*aca3beaaSApple OSS Distributions 		pid_t rc = waitpid(child_pid, &status, 0);
356*aca3beaaSApple OSS Distributions 		T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
357*aca3beaaSApple OSS Distributions 		code = WEXITSTATUS(status);
358*aca3beaaSApple OSS Distributions 		T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly");
359*aca3beaaSApple OSS Distributions 		T_END;
360*aca3beaaSApple OSS Distributions 	});
361*aca3beaaSApple OSS Distributions }
362*aca3beaaSApple OSS Distributions 
363*aca3beaaSApple OSS Distributions T_DECL(vm_reclaim_limit_kills, "Deferred reclaims are processed before a limit kill")
364*aca3beaaSApple OSS Distributions {
365*aca3beaaSApple OSS Distributions 	int err;
366*aca3beaaSApple OSS Distributions 	struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
367*aca3beaaSApple OSS Distributions 	const size_t kNumEntries = 50;
368*aca3beaaSApple OSS Distributions 	static const size_t kAllocationSize = (1UL << 20); // 1MB
369*aca3beaaSApple OSS Distributions 	static const size_t kMemoryLimit = kNumEntries / 10 * kAllocationSize;
370*aca3beaaSApple OSS Distributions 
371*aca3beaaSApple OSS Distributions 	kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
372*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
373*aca3beaaSApple OSS Distributions 
374*aca3beaaSApple OSS Distributions 	err = set_memlimits(getpid(), kMemoryLimit >> 20, kMemoryLimit >> 20, TRUE, TRUE);
375*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_POSIX_SUCCESS(err, "set_memlimits");
376*aca3beaaSApple OSS Distributions 
377*aca3beaaSApple OSS Distributions 	for (size_t i = 0; i < kNumEntries; i++) {
378*aca3beaaSApple OSS Distributions 		mach_vm_address_t addr = 0;
379*aca3beaaSApple OSS Distributions 		uint64_t idx = allocate_and_defer_free(kAllocationSize, &ringbuffer, (unsigned char) i, &addr);
380*aca3beaaSApple OSS Distributions 		T_QUIET; T_ASSERT_EQ(idx, (uint64_t) i, "idx is correct");
381*aca3beaaSApple OSS Distributions 	}
382*aca3beaaSApple OSS Distributions 
383*aca3beaaSApple 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);
384*aca3beaaSApple OSS Distributions }
385*aca3beaaSApple OSS Distributions 
386*aca3beaaSApple OSS Distributions T_DECL(vm_reclaim_update_reclaimable_bytes_threshold, "Kernel reclaims when num_bytes_reclaimable crosses threshold")
387*aca3beaaSApple OSS Distributions {
388*aca3beaaSApple OSS Distributions 	mach_vm_size_t kNumEntries = 0;
389*aca3beaaSApple OSS Distributions 	struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
390*aca3beaaSApple OSS Distributions 	const size_t kAllocationSize = vm_kernel_page_size;
391*aca3beaaSApple OSS Distributions 	uint64_t vm_reclaim_reclaimable_max_threshold;
392*aca3beaaSApple OSS Distributions 	int ret;
393*aca3beaaSApple OSS Distributions 	size_t len = sizeof(vm_reclaim_reclaimable_max_threshold);
394*aca3beaaSApple OSS Distributions 	size_t num_ledger_entries = 0;
395*aca3beaaSApple OSS Distributions 	size_t phys_footprint_index = ledger_phys_footprint_index(&num_ledger_entries);
396*aca3beaaSApple OSS Distributions 
397*aca3beaaSApple OSS Distributions 	kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
398*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
399*aca3beaaSApple OSS Distributions 
400*aca3beaaSApple OSS Distributions 	// Allocate 1000 times the reclaim threshold
401*aca3beaaSApple OSS Distributions 	ret = sysctlbyname("vm.reclaim_max_threshold", &vm_reclaim_reclaimable_max_threshold, &len, NULL, 0);
402*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "vm.reclaim_max_threshold");
403*aca3beaaSApple OSS Distributions 	kNumEntries = vm_reclaim_reclaimable_max_threshold / kAllocationSize * 1000;
404*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_LT(kNumEntries, ringbuffer.buffer_len, "Entries will not fill up ringbuffer.");
405*aca3beaaSApple OSS Distributions 
406*aca3beaaSApple OSS Distributions 	mach_vm_address_t addr = 0;
407*aca3beaaSApple OSS Distributions 	for (uint64_t i = 0; i < kNumEntries; i++) {
408*aca3beaaSApple OSS Distributions 		uint64_t idx = allocate_and_defer_free(kAllocationSize, &ringbuffer, (unsigned char) i, &addr);
409*aca3beaaSApple OSS Distributions 		T_QUIET; T_ASSERT_EQ(idx, i, "idx is correct");
410*aca3beaaSApple OSS Distributions 	}
411*aca3beaaSApple OSS Distributions 
412*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_LT(get_ledger_entry_for_pid(getpid(), phys_footprint_index, num_ledger_entries),
413*aca3beaaSApple OSS Distributions 	    (int64_t) ((kNumEntries) * kAllocationSize), "Entries were reclaimed as we crossed threshold");
414*aca3beaaSApple OSS Distributions }
415*aca3beaaSApple OSS Distributions 
416*aca3beaaSApple OSS Distributions T_HELPER_DECL(deallocate_indices,
417*aca3beaaSApple OSS Distributions     "deallocate the indices from underneath the kernel")
418*aca3beaaSApple OSS Distributions {
419*aca3beaaSApple OSS Distributions 	mach_vm_reclaim_ringbuffer_v1_t ringbuffer = NULL;
420*aca3beaaSApple OSS Distributions 	static const size_t kAllocationSize = (1UL << 20); // 1MB
421*aca3beaaSApple OSS Distributions 	mach_vm_address_t addr;
422*aca3beaaSApple OSS Distributions 
423*aca3beaaSApple OSS Distributions 	kern_return_t kr;
424*aca3beaaSApple OSS Distributions 	kr = mach_vm_map(mach_task_self(), (mach_vm_address_t *) &ringbuffer, vm_page_size, 0, VM_FLAGS_ANYWHERE, MEMORY_OBJECT_NULL, 0, FALSE, VM_PROT_DEFAULT, VM_PROT_ALL, VM_INHERIT_DEFAULT);
425*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_map");
426*aca3beaaSApple OSS Distributions 	kr = mach_vm_reclaim_ringbuffer_init(ringbuffer);
427*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
428*aca3beaaSApple OSS Distributions 
429*aca3beaaSApple OSS Distributions 	uint64_t idx = allocate_and_defer_free(kAllocationSize, ringbuffer, 1, &addr);
430*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_EQ(idx, 0ULL, "Entry placed at start of buffer");
431*aca3beaaSApple OSS Distributions 	kr = mach_vm_deallocate(mach_task_self(), (mach_vm_address_t) ringbuffer, vm_page_size);
432*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_deallocate");
433*aca3beaaSApple OSS Distributions 
434*aca3beaaSApple OSS Distributions 	mach_vm_reclaim_synchronize(ringbuffer, 10);
435*aca3beaaSApple OSS Distributions 
436*aca3beaaSApple OSS Distributions 	T_FAIL("Test did not crash when synchronizing with deallocated indices");
437*aca3beaaSApple OSS Distributions }
438*aca3beaaSApple OSS Distributions 
439*aca3beaaSApple OSS Distributions T_DECL(vm_reclaim_copyio_indices_error, "Force a copyio error on the indices",
440*aca3beaaSApple OSS Distributions     T_META_IGNORECRASHES("vm_reclaim_copyio_indices_error*"))
441*aca3beaaSApple OSS Distributions {
442*aca3beaaSApple OSS Distributions 	int status = spawn_helper_and_wait_for_exit("deallocate_indices");
443*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(status), "Test process crashed.");
444*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_EQ(WTERMSIG(status), SIGKILL, "Test process crashed with SIGKILL.");
445*aca3beaaSApple OSS Distributions }
446*aca3beaaSApple OSS Distributions 
447*aca3beaaSApple OSS Distributions T_HELPER_DECL(deallocate_buffer,
448*aca3beaaSApple OSS Distributions     "deallocate the buffer from underneath the kernel")
449*aca3beaaSApple OSS Distributions {
450*aca3beaaSApple OSS Distributions 	struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
451*aca3beaaSApple OSS Distributions 	static const size_t kAllocationSize = (1UL << 20); // 1MB
452*aca3beaaSApple OSS Distributions 	mach_vm_address_t addr;
453*aca3beaaSApple OSS Distributions 
454*aca3beaaSApple OSS Distributions 	kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
455*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
456*aca3beaaSApple OSS Distributions 
457*aca3beaaSApple OSS Distributions 	uint64_t idx = allocate_and_defer_free(kAllocationSize, &ringbuffer, 1, &addr);
458*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_EQ(idx, 0ULL, "Entry placed at start of buffer");
459*aca3beaaSApple OSS Distributions 	kr = mach_vm_deallocate(mach_task_self(), (mach_vm_address_t) ringbuffer.buffer, ringbuffer.buffer_len * sizeof(mach_vm_reclaim_entry_v1_t));
460*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_deallocate");
461*aca3beaaSApple OSS Distributions 
462*aca3beaaSApple OSS Distributions 	mach_vm_reclaim_synchronize(&ringbuffer, 10);
463*aca3beaaSApple OSS Distributions 
464*aca3beaaSApple OSS Distributions 	T_FAIL("Test did not crash when synchronizing on a deallocated buffer!");
465*aca3beaaSApple OSS Distributions }
466*aca3beaaSApple OSS Distributions 
467*aca3beaaSApple OSS Distributions T_DECL(vm_reclaim_copyio_buffer_error, "Force a copyio error on the buffer",
468*aca3beaaSApple OSS Distributions     T_META_IGNORECRASHES("vm_reclaim_copyio_buffer_error*"),
469*aca3beaaSApple OSS Distributions     T_META_BOOTARGS_SET(VM_RECLAIM_THRESHOLD_BOOT_ARG))
470*aca3beaaSApple OSS Distributions {
471*aca3beaaSApple OSS Distributions 	int status = spawn_helper_and_wait_for_exit("deallocate_buffer");
472*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(status), "Test process crashed.");
473*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_EQ(WTERMSIG(status), SIGKILL, "Test process crashed with SIGKILL.");
474*aca3beaaSApple OSS Distributions }
475*aca3beaaSApple OSS Distributions 
476*aca3beaaSApple OSS Distributions T_HELPER_DECL(dealloc_gap, "Put a bad entry in the buffer")
477*aca3beaaSApple OSS Distributions {
478*aca3beaaSApple OSS Distributions 	struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
479*aca3beaaSApple OSS Distributions 	static const size_t kAllocationSize = (1UL << 20); // 1MB
480*aca3beaaSApple OSS Distributions 	mach_vm_address_t addr;
481*aca3beaaSApple OSS Distributions 	bool should_update_kernel_accounting = false;
482*aca3beaaSApple OSS Distributions 
483*aca3beaaSApple OSS Distributions 	kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
484*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
485*aca3beaaSApple OSS Distributions 
486*aca3beaaSApple OSS Distributions 	uint64_t idx = allocate_and_defer_free(kAllocationSize, &ringbuffer, 1, &addr);
487*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_EQ(idx, 0ULL, "Entry placed at start of buffer");
488*aca3beaaSApple OSS Distributions 	idx = mach_vm_reclaim_mark_free(&ringbuffer, addr, (uint32_t) kAllocationSize, &should_update_kernel_accounting);
489*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_EQ(idx, 1ULL, "Entry placed at correct index");
490*aca3beaaSApple OSS Distributions 
491*aca3beaaSApple OSS Distributions 	mach_vm_reclaim_synchronize(&ringbuffer, 2);
492*aca3beaaSApple OSS Distributions 
493*aca3beaaSApple OSS Distributions 	T_FAIL("Test did not crash when doing a double free!");
494*aca3beaaSApple OSS Distributions }
495*aca3beaaSApple OSS Distributions 
496*aca3beaaSApple OSS Distributions T_DECL(vm_reclaim_dealloc_gap, "Ensure a dealloc gap delivers a fatal exception",
497*aca3beaaSApple OSS Distributions     T_META_IGNORECRASHES("vm_reclaim_dealloc_gap*"))
498*aca3beaaSApple OSS Distributions {
499*aca3beaaSApple OSS Distributions 	int status = spawn_helper_and_wait_for_exit("dealloc_gap");
500*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(status), "Test process crashed.");
501*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_EQ(WTERMSIG(status), SIGKILL, "Test process crashed with SIGKILL.");
502*aca3beaaSApple OSS Distributions }
503*aca3beaaSApple OSS Distributions 
504*aca3beaaSApple OSS Distributions T_HELPER_DECL(allocate_and_suspend_with_dealloc_gap,
505*aca3beaaSApple OSS Distributions     "defer double free, and signal parent to suspend")
506*aca3beaaSApple OSS Distributions {
507*aca3beaaSApple OSS Distributions 	allocate_and_suspend(argv, false, true);
508*aca3beaaSApple OSS Distributions }
509*aca3beaaSApple OSS Distributions 
510*aca3beaaSApple OSS Distributions static void
vm_reclaim_async_exception(char * variant,char * arg1)511*aca3beaaSApple OSS Distributions vm_reclaim_async_exception(char *variant, char *arg1)
512*aca3beaaSApple OSS Distributions {
513*aca3beaaSApple OSS Distributions 	test_after_background_helper_launches(variant, arg1, ^{
514*aca3beaaSApple OSS Distributions 		int ret = 0;
515*aca3beaaSApple OSS Distributions 		ret = pid_suspend(child_pid);
516*aca3beaaSApple OSS Distributions 		T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
517*aca3beaaSApple OSS Distributions 		/*
518*aca3beaaSApple OSS Distributions 		 * The reclaim work is kicked off asynchronously by the suspend.
519*aca3beaaSApple OSS Distributions 		 * So we need to call into the kernel to synchronize with the reclaim worker
520*aca3beaaSApple OSS Distributions 		 * thread.
521*aca3beaaSApple OSS Distributions 		 */
522*aca3beaaSApple OSS Distributions 		drain_async_queue(child_pid);
523*aca3beaaSApple OSS Distributions 	}, ^{
524*aca3beaaSApple OSS Distributions 		int status;
525*aca3beaaSApple OSS Distributions 		pid_t rc = waitpid(child_pid, &status, 0);
526*aca3beaaSApple OSS Distributions 		T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
527*aca3beaaSApple OSS Distributions 		T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(status), "Test process crashed.");
528*aca3beaaSApple OSS Distributions 		T_QUIET; T_ASSERT_EQ(WTERMSIG(status), SIGKILL, "Test process crashed with SIGKILL.");
529*aca3beaaSApple OSS Distributions 		T_END;
530*aca3beaaSApple OSS Distributions 	});
531*aca3beaaSApple OSS Distributions }
532*aca3beaaSApple OSS Distributions 
533*aca3beaaSApple OSS Distributions T_DECL(vm_reclaim_dealloc_gap_async, "Ensure a dealloc gap delivers an async fatal exception",
534*aca3beaaSApple OSS Distributions     T_META_IGNORECRASHES("vm_reclaim_dealloc_gap_async*"))
535*aca3beaaSApple OSS Distributions {
536*aca3beaaSApple OSS Distributions 	vm_reclaim_async_exception("allocate_and_suspend_with_dealloc_gap", "15");
537*aca3beaaSApple OSS Distributions }
538*aca3beaaSApple OSS Distributions 
539*aca3beaaSApple OSS Distributions T_HELPER_DECL(allocate_and_suspend_with_buffer_error,
540*aca3beaaSApple OSS Distributions     "defer free, free buffer, and signal parent to suspend")
541*aca3beaaSApple OSS Distributions {
542*aca3beaaSApple OSS Distributions 	allocate_and_suspend(argv, true, false);
543*aca3beaaSApple OSS Distributions }
544*aca3beaaSApple OSS Distributions 
545*aca3beaaSApple OSS Distributions T_DECL(vm_reclaim_copyio_buffer_error_async, "Ensure a buffer copyio failure delivers an async fatal exception",
546*aca3beaaSApple OSS Distributions     T_META_IGNORECRASHES("vm_reclaim_dealloc_gap_async*"),
547*aca3beaaSApple OSS Distributions     T_META_BOOTARGS_SET(VM_RECLAIM_THRESHOLD_BOOT_ARG))
548*aca3beaaSApple OSS Distributions {
549*aca3beaaSApple OSS Distributions 	vm_reclaim_async_exception("allocate_and_suspend_with_buffer_error", "15");
550*aca3beaaSApple OSS Distributions }
551*aca3beaaSApple OSS Distributions 
552*aca3beaaSApple OSS Distributions T_HELPER_DECL(reuse_freed_entry_fork,
553*aca3beaaSApple OSS Distributions     "defer free, sync, and try to use entry")
554*aca3beaaSApple OSS Distributions {
555*aca3beaaSApple OSS Distributions 	struct mach_vm_reclaim_ringbuffer_v1_s ringbuffer;
556*aca3beaaSApple OSS Distributions 	static const size_t kAllocationSize = (1UL << 20); // 1MB
557*aca3beaaSApple OSS Distributions 	mach_vm_address_t addr;
558*aca3beaaSApple OSS Distributions 	static const unsigned char kValue = 119;
559*aca3beaaSApple OSS Distributions 
560*aca3beaaSApple OSS Distributions 	kern_return_t kr = mach_vm_reclaim_ringbuffer_init(&ringbuffer);
561*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_ringbuffer_init");
562*aca3beaaSApple OSS Distributions 
563*aca3beaaSApple OSS Distributions 	uint64_t idx = allocate_and_defer_free(kAllocationSize, &ringbuffer, kValue, &addr);
564*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_EQ(idx, 0ULL, "Entry placed at start of buffer");
565*aca3beaaSApple OSS Distributions 
566*aca3beaaSApple OSS Distributions 	pid_t forked_pid = fork();
567*aca3beaaSApple OSS Distributions 	T_QUIET; T_WITH_ERRNO; T_ASSERT_NE(forked_pid, -1, "fork()");
568*aca3beaaSApple OSS Distributions 	if (forked_pid == 0) {
569*aca3beaaSApple OSS Distributions 		kr = mach_vm_reclaim_synchronize(&ringbuffer, 10);
570*aca3beaaSApple OSS Distributions 		T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_reclaim_synchronize");
571*aca3beaaSApple OSS Distributions 		assert_buffer_has_changed_and_crash(addr, kAllocationSize, kValue);
572*aca3beaaSApple OSS Distributions 	} else {
573*aca3beaaSApple OSS Distributions 		int status;
574*aca3beaaSApple OSS Distributions 		pid_t rc = waitpid(forked_pid, &status, 0);
575*aca3beaaSApple OSS Distributions 		T_QUIET; T_ASSERT_EQ(rc, forked_pid, "waitpid");
576*aca3beaaSApple OSS Distributions 		T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(status), "Forked process crashed.");
577*aca3beaaSApple OSS Distributions 		T_QUIET; T_ASSERT_EQ(WTERMSIG(status), SIGSEGV, "Forked process crashed with segmentation fault.");
578*aca3beaaSApple OSS Distributions 	}
579*aca3beaaSApple OSS Distributions }
580*aca3beaaSApple OSS Distributions 
581*aca3beaaSApple OSS Distributions T_DECL(vm_reclaim_fork, "Ensure reclaim buffer is inherited across a fork",
582*aca3beaaSApple OSS Distributions     T_META_IGNORECRASHES("vm_reclaim_fork*"),
583*aca3beaaSApple OSS Distributions     T_META_BOOTARGS_SET(VM_RECLAIM_THRESHOLD_BOOT_ARG))
584*aca3beaaSApple OSS Distributions {
585*aca3beaaSApple OSS Distributions 	int status = spawn_helper_and_wait_for_exit("reuse_freed_entry_fork");
586*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_TRUE(WIFEXITED(status), "Test process exited.");
587*aca3beaaSApple OSS Distributions 	T_QUIET; T_ASSERT_EQ(WEXITSTATUS(status), 0, "Test process exited cleanly.");
588*aca3beaaSApple OSS Distributions }
589