xref: /xnu-11417.121.6/tests/stackshot_accuracy.m (revision a1e26a70f38d1d7daa7b49b258e2f8538ad81650)
1*a1e26a70SApple OSS Distributions#include <darwintest.h>
2*a1e26a70SApple OSS Distributions#include <darwintest_utils.h>
3*a1e26a70SApple OSS Distributions#include <sys/kern_memorystatus.h>
4*a1e26a70SApple OSS Distributions#include <kern/debug.h>
5*a1e26a70SApple OSS Distributions#include <mach-o/dyld.h>
6*a1e26a70SApple OSS Distributions#include <sys/stackshot.h>
7*a1e26a70SApple OSS Distributions#include <kdd.h>
8*a1e26a70SApple OSS Distributions#include <signal.h>
9*a1e26a70SApple OSS Distributions#include "test_utils.h"
10*a1e26a70SApple OSS Distributions
11*a1e26a70SApple OSS Distributions#define RECURSIONS 25
12*a1e26a70SApple OSS Distributions#define FIRST_RECURSIVE_FRAME 3
13*a1e26a70SApple OSS Distributions
14*a1e26a70SApple OSS DistributionsT_GLOBAL_META(
15*a1e26a70SApple OSS Distributions		T_META_NAMESPACE("xnu.stackshot.accuracy"),
16*a1e26a70SApple OSS Distributions		T_META_RADAR_COMPONENT_NAME("xnu"),
17*a1e26a70SApple OSS Distributions		T_META_RADAR_COMPONENT_VERSION("stackshot"),
18*a1e26a70SApple OSS Distributions		T_META_OWNER("jonathan_w_adams"),
19*a1e26a70SApple OSS Distributions		T_META_CHECK_LEAKS(false),
20*a1e26a70SApple OSS Distributions		T_META_ASROOT(true),
21*a1e26a70SApple OSS Distributions		XNU_T_META_SOC_SPECIFIC
22*a1e26a70SApple OSS Distributions		);
23*a1e26a70SApple OSS Distributions
24*a1e26a70SApple OSS Distributions
25*a1e26a70SApple OSS Distributionsvoid child_init(void);
26*a1e26a70SApple OSS Distributionsvoid parent_helper_singleproc(int);
27*a1e26a70SApple OSS Distributions
28*a1e26a70SApple OSS Distributions#define CHECK_FOR_FAULT_STATS         (1 << 0)
29*a1e26a70SApple OSS Distributions#define WRITE_STACKSHOT_BUFFER_TO_TMP (1 << 1)
30*a1e26a70SApple OSS Distributions#define CHECK_FOR_KERNEL_THREADS      (1 << 2)
31*a1e26a70SApple OSS Distributionsint check_stackshot(void *, int);
32*a1e26a70SApple OSS Distributions
33*a1e26a70SApple OSS Distributions/* used for WRITE_STACKSHOT_BUFFER_TO_TMP */
34*a1e26a70SApple OSS Distributionsstatic char const *current_scenario_name;
35*a1e26a70SApple OSS Distributionsstatic pid_t child_pid;
36*a1e26a70SApple OSS Distributions
37*a1e26a70SApple OSS Distributions/* helpers */
38*a1e26a70SApple OSS Distributions
39*a1e26a70SApple OSS Distributionsstatic void __attribute__((noinline))
40*a1e26a70SApple OSS Distributionschild_recurse(int r, int spin, void (^cb)(void))
41*a1e26a70SApple OSS Distributions{
42*a1e26a70SApple OSS Distributions	if (r > 0) {
43*a1e26a70SApple OSS Distributions		child_recurse(r - 1, spin, cb);
44*a1e26a70SApple OSS Distributions	}
45*a1e26a70SApple OSS Distributions
46*a1e26a70SApple OSS Distributions	cb();
47*a1e26a70SApple OSS Distributions
48*a1e26a70SApple OSS Distributions	/* wait forever */
49*a1e26a70SApple OSS Distributions	if (spin == 0) {
50*a1e26a70SApple OSS Distributions		sleep(100000);
51*a1e26a70SApple OSS Distributions	} else if (spin == 2) {
52*a1e26a70SApple OSS Distributions		int v = 1;
53*a1e26a70SApple OSS Distributions		/* ssh won't let the session die if we still have file handles open to its output. */
54*a1e26a70SApple OSS Distributions		close(STDERR_FILENO);
55*a1e26a70SApple OSS Distributions		close(STDOUT_FILENO);
56*a1e26a70SApple OSS Distributions		T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.wedge_thread", NULL, NULL, &v, sizeof(v)),
57*a1e26a70SApple OSS Distributions					"wedged thread in the kernel");
58*a1e26a70SApple OSS Distributions	} else {
59*a1e26a70SApple OSS Distributions		while (1) {
60*a1e26a70SApple OSS Distributions			__asm__ volatile("" : : : "memory");
61*a1e26a70SApple OSS Distributions		}
62*a1e26a70SApple OSS Distributions	}
63*a1e26a70SApple OSS Distributions}
64*a1e26a70SApple OSS Distributions
65*a1e26a70SApple OSS DistributionsT_HELPER_DECL(simple_child_process, "child process that will be frozen and others")
66*a1e26a70SApple OSS Distributions{
67*a1e26a70SApple OSS Distributions	child_init();
68*a1e26a70SApple OSS Distributions}
69*a1e26a70SApple OSS Distributions
70*a1e26a70SApple OSS DistributionsT_HELPER_DECL(sid_child_process, "child process that setsid()s")
71*a1e26a70SApple OSS Distributions{
72*a1e26a70SApple OSS Distributions	pid_t ppid = getppid();
73*a1e26a70SApple OSS Distributions
74*a1e26a70SApple OSS Distributions	T_ASSERT_POSIX_SUCCESS(setsid(), "session id set");
75*a1e26a70SApple OSS Distributions
76*a1e26a70SApple OSS Distributions	child_recurse(RECURSIONS, 2, ^{
77*a1e26a70SApple OSS Distributions		kill(ppid, SIGUSR1);
78*a1e26a70SApple OSS Distributions	});
79*a1e26a70SApple OSS Distributions
80*a1e26a70SApple OSS Distributions	T_ASSERT_FAIL("child_init returned!");
81*a1e26a70SApple OSS Distributions}
82*a1e26a70SApple OSS Distributions
83*a1e26a70SApple OSS Distributionsstatic void
84*a1e26a70SApple OSS Distributionskill_children(void)
85*a1e26a70SApple OSS Distributions{
86*a1e26a70SApple OSS Distributions	kill(child_pid, SIGKILL);
87*a1e26a70SApple OSS Distributions}
88*a1e26a70SApple OSS Distributions
89*a1e26a70SApple OSS Distributionsstatic void *
90*a1e26a70SApple OSS Distributionstake_stackshot(pid_t target_pid, uint64_t extra_flags, uint64_t since_timestamp)
91*a1e26a70SApple OSS Distributions{
92*a1e26a70SApple OSS Distributions	void *stackshot_config;
93*a1e26a70SApple OSS Distributions	int err, retries = 5;
94*a1e26a70SApple OSS Distributions	uint64_t stackshot_flags = STACKSHOT_KCDATA_FORMAT |
95*a1e26a70SApple OSS Distributions								STACKSHOT_THREAD_WAITINFO |
96*a1e26a70SApple OSS Distributions								STACKSHOT_GET_DQ;
97*a1e26a70SApple OSS Distributions
98*a1e26a70SApple OSS Distributions	/* we should be able to verify delta stackshots */
99*a1e26a70SApple OSS Distributions	if (since_timestamp != 0) {
100*a1e26a70SApple OSS Distributions		stackshot_flags |= STACKSHOT_COLLECT_DELTA_SNAPSHOT;
101*a1e26a70SApple OSS Distributions	}
102*a1e26a70SApple OSS Distributions
103*a1e26a70SApple OSS Distributions	stackshot_flags |= extra_flags;
104*a1e26a70SApple OSS Distributions
105*a1e26a70SApple OSS Distributions	stackshot_config = stackshot_config_create();
106*a1e26a70SApple OSS Distributions	T_ASSERT_NOTNULL(stackshot_config, "allocate stackshot config");
107*a1e26a70SApple OSS Distributions
108*a1e26a70SApple OSS Distributions	err = stackshot_config_set_flags(stackshot_config, stackshot_flags);
109*a1e26a70SApple OSS Distributions	T_ASSERT_EQ(err, 0, "set flags on stackshot config");
110*a1e26a70SApple OSS Distributions
111*a1e26a70SApple OSS Distributions	err = stackshot_config_set_pid(stackshot_config, target_pid);
112*a1e26a70SApple OSS Distributions	T_ASSERT_EQ(err, 0, "set target pid on stackshot config");
113*a1e26a70SApple OSS Distributions
114*a1e26a70SApple OSS Distributions	if (since_timestamp != 0) {
115*a1e26a70SApple OSS Distributions		err = stackshot_config_set_delta_timestamp(stackshot_config, since_timestamp);
116*a1e26a70SApple OSS Distributions		T_ASSERT_EQ(err, 0, "set prev snapshot time on stackshot config");
117*a1e26a70SApple OSS Distributions	}
118*a1e26a70SApple OSS Distributions
119*a1e26a70SApple OSS Distributions	while (retries > 0) {
120*a1e26a70SApple OSS Distributions		err = stackshot_capture_with_config(stackshot_config);
121*a1e26a70SApple OSS Distributions		if (err == 0) {
122*a1e26a70SApple OSS Distributions			break;
123*a1e26a70SApple OSS Distributions		} else if (err == EBUSY || err == ETIMEDOUT) {
124*a1e26a70SApple OSS Distributions			T_LOG("stackshot capture returned %d (%s)\n", err, strerror(err));
125*a1e26a70SApple OSS Distributions			if (retries == 0) {
126*a1e26a70SApple OSS Distributions				T_ASSERT_FAIL("failed to take stackshot with error after retries: %d: %s\n", err, strerror(err));
127*a1e26a70SApple OSS Distributions			}
128*a1e26a70SApple OSS Distributions
129*a1e26a70SApple OSS Distributions			retries--;
130*a1e26a70SApple OSS Distributions			continue;
131*a1e26a70SApple OSS Distributions		} else {
132*a1e26a70SApple OSS Distributions			T_ASSERT_FAIL("failed to take stackshot with error: %d: %s\n", err, strerror(err));
133*a1e26a70SApple OSS Distributions		}
134*a1e26a70SApple OSS Distributions	}
135*a1e26a70SApple OSS Distributions
136*a1e26a70SApple OSS Distributions	return stackshot_config;
137*a1e26a70SApple OSS Distributions}
138*a1e26a70SApple OSS Distributions
139*a1e26a70SApple OSS Distributionsint
140*a1e26a70SApple OSS Distributionscheck_stackshot(void *stackshot_config, int flags)
141*a1e26a70SApple OSS Distributions{
142*a1e26a70SApple OSS Distributions	void *buf;
143*a1e26a70SApple OSS Distributions	uint32_t buflen, kcdata_type;
144*a1e26a70SApple OSS Distributions	kcdata_iter_t iter;
145*a1e26a70SApple OSS Distributions	NSError *nserror = nil;
146*a1e26a70SApple OSS Distributions	pid_t target_pid;
147*a1e26a70SApple OSS Distributions	int ret = 0;
148*a1e26a70SApple OSS Distributions	uint64_t expected_return_addr = 0;
149*a1e26a70SApple OSS Distributions	bool found_fault_stats = false;
150*a1e26a70SApple OSS Distributions	struct stackshot_fault_stats fault_stats = {0};
151*a1e26a70SApple OSS Distributions
152*a1e26a70SApple OSS Distributions	buf = stackshot_config_get_stackshot_buffer(stackshot_config);
153*a1e26a70SApple OSS Distributions	T_ASSERT_NOTNULL(buf, "stackshot buffer is not null");
154*a1e26a70SApple OSS Distributions	buflen = stackshot_config_get_stackshot_size(stackshot_config);
155*a1e26a70SApple OSS Distributions	T_ASSERT_GT(buflen, 0, "valid stackshot buffer length");
156*a1e26a70SApple OSS Distributions	target_pid = ((struct stackshot_config*)stackshot_config)->sc_pid;
157*a1e26a70SApple OSS Distributions	T_ASSERT_GT(target_pid, 0, "valid target_pid");
158*a1e26a70SApple OSS Distributions
159*a1e26a70SApple OSS Distributions	/* if need to write it to fs, do it now */
160*a1e26a70SApple OSS Distributions	if (flags & WRITE_STACKSHOT_BUFFER_TO_TMP) {
161*a1e26a70SApple OSS Distributions		char sspath[MAXPATHLEN];
162*a1e26a70SApple OSS Distributions		strlcpy(sspath, current_scenario_name, sizeof(sspath));
163*a1e26a70SApple OSS Distributions		strlcat(sspath, ".kcdata", sizeof(sspath));
164*a1e26a70SApple OSS Distributions		T_QUIET; T_ASSERT_POSIX_ZERO(dt_resultfile(sspath, sizeof(sspath)),
165*a1e26a70SApple OSS Distributions				"create result file path");
166*a1e26a70SApple OSS Distributions
167*a1e26a70SApple OSS Distributions		FILE *f = fopen(sspath, "w");
168*a1e26a70SApple OSS Distributions		T_WITH_ERRNO; T_QUIET; T_ASSERT_NOTNULL(f,
169*a1e26a70SApple OSS Distributions				"open stackshot output file");
170*a1e26a70SApple OSS Distributions
171*a1e26a70SApple OSS Distributions		size_t written = fwrite(buf, buflen, 1, f);
172*a1e26a70SApple OSS Distributions		T_QUIET; T_ASSERT_POSIX_SUCCESS(written, "wrote stackshot to file");
173*a1e26a70SApple OSS Distributions
174*a1e26a70SApple OSS Distributions		fclose(f);
175*a1e26a70SApple OSS Distributions	}
176*a1e26a70SApple OSS Distributions
177*a1e26a70SApple OSS Distributions	/* begin iterating */
178*a1e26a70SApple OSS Distributions	iter = kcdata_iter(buf, buflen);
179*a1e26a70SApple OSS Distributions	T_ASSERT_EQ(kcdata_iter_type(iter), KCDATA_BUFFER_BEGIN_STACKSHOT, "buffer is a stackshot");
180*a1e26a70SApple OSS Distributions
181*a1e26a70SApple OSS Distributions	/* time to iterate */
182*a1e26a70SApple OSS Distributions	iter = kcdata_iter_next(iter);
183*a1e26a70SApple OSS Distributions	KCDATA_ITER_FOREACH(iter) {
184*a1e26a70SApple OSS Distributions		kcdata_type = kcdata_iter_type(iter);
185*a1e26a70SApple OSS Distributions		NSNumber *parsedPid;
186*a1e26a70SApple OSS Distributions		NSMutableDictionary *parsedContainer, *parsedThreads;
187*a1e26a70SApple OSS Distributions
188*a1e26a70SApple OSS Distributions		if ((flags & CHECK_FOR_FAULT_STATS) != 0 &&
189*a1e26a70SApple OSS Distributions				kcdata_type == STACKSHOT_KCTYPE_STACKSHOT_FAULT_STATS) {
190*a1e26a70SApple OSS Distributions			memcpy(&fault_stats, kcdata_iter_payload(iter), sizeof(fault_stats));
191*a1e26a70SApple OSS Distributions			found_fault_stats = true;
192*a1e26a70SApple OSS Distributions		}
193*a1e26a70SApple OSS Distributions
194*a1e26a70SApple OSS Distributions		if (kcdata_type != KCDATA_TYPE_CONTAINER_BEGIN) {
195*a1e26a70SApple OSS Distributions			continue;
196*a1e26a70SApple OSS Distributions		}
197*a1e26a70SApple OSS Distributions
198*a1e26a70SApple OSS Distributions		if (kcdata_iter_container_type(iter) != STACKSHOT_KCCONTAINER_TASK) {
199*a1e26a70SApple OSS Distributions			continue;
200*a1e26a70SApple OSS Distributions		}
201*a1e26a70SApple OSS Distributions
202*a1e26a70SApple OSS Distributions		parsedContainer = parseKCDataContainer(&iter, &nserror);
203*a1e26a70SApple OSS Distributions		T_ASSERT_NOTNULL(parsedContainer, "parsedContainer is not null");
204*a1e26a70SApple OSS Distributions		T_ASSERT_NULL(nserror, "no NSError occured while parsing the kcdata container");
205*a1e26a70SApple OSS Distributions
206*a1e26a70SApple OSS Distributions		/*
207*a1e26a70SApple OSS Distributions		 * given that we've targetted the pid, we can be sure that this
208*a1e26a70SApple OSS Distributions		 * ts_pid will be the pid we expect
209*a1e26a70SApple OSS Distributions		 */
210*a1e26a70SApple OSS Distributions		parsedPid = parsedContainer[@"task_snapshots"][@"task_snapshot"][@"ts_pid"];
211*a1e26a70SApple OSS Distributions		T_ASSERT_EQ([parsedPid intValue], target_pid, "found correct pid");
212*a1e26a70SApple OSS Distributions
213*a1e26a70SApple OSS Distributions		/* start parsing the threads */
214*a1e26a70SApple OSS Distributions		parsedThreads = parsedContainer[@"task_snapshots"][@"thread_snapshots"];
215*a1e26a70SApple OSS Distributions		for (id th_key in parsedThreads) {
216*a1e26a70SApple OSS Distributions			uint32_t frame_index = 0;
217*a1e26a70SApple OSS Distributions
218*a1e26a70SApple OSS Distributions			if ((flags & CHECK_FOR_KERNEL_THREADS) == 0) {
219*a1e26a70SApple OSS Distributions				/* skip threads that don't have enough frames */
220*a1e26a70SApple OSS Distributions				if ([parsedThreads[th_key][@"user_stack_frames"] count] < RECURSIONS) {
221*a1e26a70SApple OSS Distributions					continue;
222*a1e26a70SApple OSS Distributions				}
223*a1e26a70SApple OSS Distributions
224*a1e26a70SApple OSS Distributions				for (id frame in parsedThreads[th_key][@"user_stack_frames"]) {
225*a1e26a70SApple OSS Distributions					if ((frame_index >= FIRST_RECURSIVE_FRAME) && (frame_index < (RECURSIONS - FIRST_RECURSIVE_FRAME))) {
226*a1e26a70SApple OSS Distributions						if (expected_return_addr == 0ull) {
227*a1e26a70SApple OSS Distributions							expected_return_addr = [frame[@"lr"] unsignedLongLongValue];
228*a1e26a70SApple OSS Distributions						} else {
229*a1e26a70SApple OSS Distributions							T_QUIET;
230*a1e26a70SApple OSS Distributions							T_ASSERT_EQ(expected_return_addr, [frame[@"lr"] unsignedLongLongValue], "expected return address found");
231*a1e26a70SApple OSS Distributions						}
232*a1e26a70SApple OSS Distributions					}
233*a1e26a70SApple OSS Distributions					frame_index ++;
234*a1e26a70SApple OSS Distributions				}
235*a1e26a70SApple OSS Distributions			} else {
236*a1e26a70SApple OSS Distributions				T_ASSERT_NOTNULL(parsedThreads[th_key][@"kernel_stack_frames"],
237*a1e26a70SApple OSS Distributions						"found kernel stack frames");
238*a1e26a70SApple OSS Distributions			}
239*a1e26a70SApple OSS Distributions
240*a1e26a70SApple OSS Distributions		}
241*a1e26a70SApple OSS Distributions	}
242*a1e26a70SApple OSS Distributions
243*a1e26a70SApple OSS Distributions	if (found_fault_stats) {
244*a1e26a70SApple OSS Distributions		T_LOG("number of pages faulted in: %d", fault_stats.sfs_pages_faulted_in);
245*a1e26a70SApple OSS Distributions		T_LOG("MATUs spent faulting: %lld", fault_stats.sfs_time_spent_faulting);
246*a1e26a70SApple OSS Distributions		T_LOG("MATUS fault time limit: %lld", fault_stats.sfs_system_max_fault_time);
247*a1e26a70SApple OSS Distributions		T_LOG("did we stop because of the limit?: %s", fault_stats.sfs_stopped_faulting ? "yes" : "no");
248*a1e26a70SApple OSS Distributions		if (expected_return_addr != 0ull) {
249*a1e26a70SApple OSS Distributions			T_ASSERT_GT(fault_stats.sfs_pages_faulted_in, 0, "faulted at least one page in");
250*a1e26a70SApple OSS Distributions			T_LOG("NOTE: successfully faulted in the pages");
251*a1e26a70SApple OSS Distributions		} else {
252*a1e26a70SApple OSS Distributions			T_LOG("NOTE: We were not able to fault the stack's pages back in");
253*a1e26a70SApple OSS Distributions
254*a1e26a70SApple OSS Distributions			/* if we couldn't fault the pages back in, then at least verify that we tried */
255*a1e26a70SApple OSS Distributions			T_ASSERT_GT(fault_stats.sfs_time_spent_faulting, 0ull, "spent time trying to fault");
256*a1e26a70SApple OSS Distributions		}
257*a1e26a70SApple OSS Distributions	} else if ((flags & CHECK_FOR_KERNEL_THREADS) == 0) {
258*a1e26a70SApple OSS Distributions		T_ASSERT_NE(expected_return_addr, 0ull, "found child thread with recursions");
259*a1e26a70SApple OSS Distributions	}
260*a1e26a70SApple OSS Distributions
261*a1e26a70SApple OSS Distributions	if (flags & CHECK_FOR_FAULT_STATS) {
262*a1e26a70SApple OSS Distributions		T_ASSERT_EQ(found_fault_stats, true, "found fault stats");
263*a1e26a70SApple OSS Distributions	}
264*a1e26a70SApple OSS Distributions
265*a1e26a70SApple OSS Distributions	return ret;
266*a1e26a70SApple OSS Distributions}
267*a1e26a70SApple OSS Distributions
268*a1e26a70SApple OSS Distributionsvoid
269*a1e26a70SApple OSS Distributionschild_init(void)
270*a1e26a70SApple OSS Distributions{
271*a1e26a70SApple OSS Distributions#if !TARGET_OS_OSX
272*a1e26a70SApple OSS Distributions	int freeze_state;
273*a1e26a70SApple OSS Distributions#endif /* !TARGET_OS_OSX */
274*a1e26a70SApple OSS Distributions	pid_t pid = getpid();
275*a1e26a70SApple OSS Distributions	char padding[16 * 1024];
276*a1e26a70SApple OSS Distributions	__asm__ volatile(""::"r"(padding));
277*a1e26a70SApple OSS Distributions
278*a1e26a70SApple OSS Distributions	T_LOG("child pid: %d\n", pid);
279*a1e26a70SApple OSS Distributions
280*a1e26a70SApple OSS Distributions#if !TARGET_OS_OSX
281*a1e26a70SApple OSS Distributions	/* allow us to be frozen */
282*a1e26a70SApple OSS Distributions	freeze_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, pid, 0, NULL, 0);
283*a1e26a70SApple OSS Distributions	if (freeze_state == 0) {
284*a1e26a70SApple OSS Distributions		T_LOG("CHILD was found to be UNFREEZABLE, enabling freezing.");
285*a1e26a70SApple OSS Distributions		memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, pid, 1, NULL, 0);
286*a1e26a70SApple OSS Distributions		freeze_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, pid, 0, NULL, 0);
287*a1e26a70SApple OSS Distributions		T_ASSERT_EQ(freeze_state, 1, "successfully set freezeability");
288*a1e26a70SApple OSS Distributions	}
289*a1e26a70SApple OSS Distributions#else
290*a1e26a70SApple OSS Distributions	T_LOG("Cannot change freezeability as freezing is only available on embedded devices");
291*a1e26a70SApple OSS Distributions#endif /* !TARGET_OS_OSX */
292*a1e26a70SApple OSS Distributions
293*a1e26a70SApple OSS Distributions	/*
294*a1e26a70SApple OSS Distributions	 * recurse a bunch of times to generate predictable data in the stackshot,
295*a1e26a70SApple OSS Distributions	 * then send SIGUSR1 to the parent to let it know that we are done.
296*a1e26a70SApple OSS Distributions	 */
297*a1e26a70SApple OSS Distributions	child_recurse(RECURSIONS, 0, ^{
298*a1e26a70SApple OSS Distributions		kill(getppid(), SIGUSR1);
299*a1e26a70SApple OSS Distributions	});
300*a1e26a70SApple OSS Distributions
301*a1e26a70SApple OSS Distributions	T_ASSERT_FAIL("child_recurse returned, but it must not?");
302*a1e26a70SApple OSS Distributions}
303*a1e26a70SApple OSS Distributions
304*a1e26a70SApple OSS Distributionsvoid
305*a1e26a70SApple OSS Distributionsparent_helper_singleproc(int spin)
306*a1e26a70SApple OSS Distributions{
307*a1e26a70SApple OSS Distributions	dispatch_semaphore_t child_done_sema = dispatch_semaphore_create(0);
308*a1e26a70SApple OSS Distributions	dispatch_queue_t dq = dispatch_queue_create("com.apple.stackshot_accuracy.basic_sp", NULL);
309*a1e26a70SApple OSS Distributions	void *stackshot_config;
310*a1e26a70SApple OSS Distributions
311*a1e26a70SApple OSS Distributions	dispatch_async(dq, ^{
312*a1e26a70SApple OSS Distributions		char padding[16 * 1024];
313*a1e26a70SApple OSS Distributions		__asm__ volatile(""::"r"(padding));
314*a1e26a70SApple OSS Distributions
315*a1e26a70SApple OSS Distributions		child_recurse(RECURSIONS, spin, ^{
316*a1e26a70SApple OSS Distributions			dispatch_semaphore_signal(child_done_sema);
317*a1e26a70SApple OSS Distributions		});
318*a1e26a70SApple OSS Distributions	});
319*a1e26a70SApple OSS Distributions
320*a1e26a70SApple OSS Distributions	dispatch_semaphore_wait(child_done_sema, DISPATCH_TIME_FOREVER);
321*a1e26a70SApple OSS Distributions	T_LOG("done waiting for child");
322*a1e26a70SApple OSS Distributions
323*a1e26a70SApple OSS Distributions	/* take the stackshot and parse it */
324*a1e26a70SApple OSS Distributions	stackshot_config = take_stackshot(getpid(), 0, 0);
325*a1e26a70SApple OSS Distributions
326*a1e26a70SApple OSS Distributions	/* check that the stackshot has the stack frames */
327*a1e26a70SApple OSS Distributions	check_stackshot(stackshot_config, WRITE_STACKSHOT_BUFFER_TO_TMP);
328*a1e26a70SApple OSS Distributions
329*a1e26a70SApple OSS Distributions	T_LOG("done!");
330*a1e26a70SApple OSS Distributions}
331*a1e26a70SApple OSS Distributions
332*a1e26a70SApple OSS DistributionsT_DECL(basic, "test that no-fault stackshot works correctly", T_META_TAG_VM_PREFERRED)
333*a1e26a70SApple OSS Distributions{
334*a1e26a70SApple OSS Distributions	char path[PATH_MAX];
335*a1e26a70SApple OSS Distributions	uint32_t path_size = sizeof(path);
336*a1e26a70SApple OSS Distributions	char *args[] = { path, "-n", "simple_child_process", NULL };
337*a1e26a70SApple OSS Distributions	dispatch_queue_t dq = dispatch_queue_create("com.apple.stackshot_accuracy.basic", NULL);
338*a1e26a70SApple OSS Distributions	dispatch_semaphore_t child_done_sema = dispatch_semaphore_create(0);
339*a1e26a70SApple OSS Distributions	dispatch_source_t child_sig_src;
340*a1e26a70SApple OSS Distributions	void *stackshot_config;
341*a1e26a70SApple OSS Distributions
342*a1e26a70SApple OSS Distributions	current_scenario_name = __func__;
343*a1e26a70SApple OSS Distributions
344*a1e26a70SApple OSS Distributions	T_LOG("parent pid: %d\n", getpid());
345*a1e26a70SApple OSS Distributions	T_QUIET; T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath");
346*a1e26a70SApple OSS Distributions
347*a1e26a70SApple OSS Distributions	/* check if we can run the child successfully */
348*a1e26a70SApple OSS Distributions#if !TARGET_OS_OSX
349*a1e26a70SApple OSS Distributions	int freeze_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
350*a1e26a70SApple OSS Distributions	if (freeze_state == -1) {
351*a1e26a70SApple OSS Distributions		T_SKIP("This device doesn't have CONFIG_FREEZE enabled.");
352*a1e26a70SApple OSS Distributions	}
353*a1e26a70SApple OSS Distributions#endif
354*a1e26a70SApple OSS Distributions
355*a1e26a70SApple OSS Distributions	/* setup signal handling */
356*a1e26a70SApple OSS Distributions	signal(SIGUSR1, SIG_IGN);
357*a1e26a70SApple OSS Distributions	child_sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dq);
358*a1e26a70SApple OSS Distributions	dispatch_source_set_event_handler(child_sig_src, ^{
359*a1e26a70SApple OSS Distributions		dispatch_semaphore_signal(child_done_sema);
360*a1e26a70SApple OSS Distributions	});
361*a1e26a70SApple OSS Distributions	dispatch_activate(child_sig_src);
362*a1e26a70SApple OSS Distributions
363*a1e26a70SApple OSS Distributions	/* create the child process */
364*a1e26a70SApple OSS Distributions	T_ASSERT_POSIX_SUCCESS(dt_launch_tool(&child_pid, args, false, NULL, NULL), "child launched");
365*a1e26a70SApple OSS Distributions	T_ATEND(kill_children);
366*a1e26a70SApple OSS Distributions
367*a1e26a70SApple OSS Distributions	/* wait until the child has recursed enough */
368*a1e26a70SApple OSS Distributions	dispatch_semaphore_wait(child_done_sema, dispatch_time(DISPATCH_TIME_NOW, 10 /*seconds*/ * 1000000000ULL));
369*a1e26a70SApple OSS Distributions
370*a1e26a70SApple OSS Distributions	T_LOG("child finished, parent executing");
371*a1e26a70SApple OSS Distributions
372*a1e26a70SApple OSS Distributions	/* take the stackshot and parse it */
373*a1e26a70SApple OSS Distributions	stackshot_config = take_stackshot(child_pid, 0, 0);
374*a1e26a70SApple OSS Distributions
375*a1e26a70SApple OSS Distributions	/* check that the stackshot has the stack frames */
376*a1e26a70SApple OSS Distributions	check_stackshot(stackshot_config, WRITE_STACKSHOT_BUFFER_TO_TMP);
377*a1e26a70SApple OSS Distributions
378*a1e26a70SApple OSS Distributions	T_LOG("all done, killing child");
379*a1e26a70SApple OSS Distributions
380*a1e26a70SApple OSS Distributions	/* tell the child to quit */
381*a1e26a70SApple OSS Distributions	T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGTERM), "killed child");
382*a1e26a70SApple OSS Distributions}
383*a1e26a70SApple OSS Distributions
384*a1e26a70SApple OSS DistributionsT_DECL(basic_singleproc, "test that no-fault stackshot works correctly in single process setting", T_META_TAG_VM_PREFERRED)
385*a1e26a70SApple OSS Distributions{
386*a1e26a70SApple OSS Distributions	current_scenario_name = __func__;
387*a1e26a70SApple OSS Distributions	parent_helper_singleproc(0);
388*a1e26a70SApple OSS Distributions}
389*a1e26a70SApple OSS Distributions
390*a1e26a70SApple OSS DistributionsT_DECL(basic_singleproc_spin, "test that no-fault stackshot works correctly in single process setting with spinning", T_META_TAG_VM_PREFERRED)
391*a1e26a70SApple OSS Distributions{
392*a1e26a70SApple OSS Distributions	current_scenario_name = __func__;
393*a1e26a70SApple OSS Distributions	parent_helper_singleproc(1);
394*a1e26a70SApple OSS Distributions}
395*a1e26a70SApple OSS Distributions
396*a1e26a70SApple OSS DistributionsT_DECL(fault, "test that faulting stackshots work correctly", T_META_TAG_VM_PREFERRED)
397*a1e26a70SApple OSS Distributions{
398*a1e26a70SApple OSS Distributions	dispatch_queue_t dq = dispatch_queue_create("com.apple.stackshot_fault_accuracy", NULL);
399*a1e26a70SApple OSS Distributions	dispatch_source_t child_sig_src;
400*a1e26a70SApple OSS Distributions	dispatch_semaphore_t child_done_sema = dispatch_semaphore_create(0);
401*a1e26a70SApple OSS Distributions	void *stackshot_config;
402*a1e26a70SApple OSS Distributions	int oldftm, newval = 1, freeze_enabled, oldratio, newratio = 0;
403*a1e26a70SApple OSS Distributions	size_t oldlen = sizeof(oldftm), fe_len = sizeof(freeze_enabled), ratiolen = sizeof(oldratio);
404*a1e26a70SApple OSS Distributions	char path[PATH_MAX];
405*a1e26a70SApple OSS Distributions	uint32_t path_size = sizeof(path);
406*a1e26a70SApple OSS Distributions	char *args[] = { path, "-n", "simple_child_process", NULL };
407*a1e26a70SApple OSS Distributions
408*a1e26a70SApple OSS Distributions	current_scenario_name = __func__;
409*a1e26a70SApple OSS Distributions	T_QUIET; T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath");
410*a1e26a70SApple OSS Distributions
411*a1e26a70SApple OSS Distributions#if TARGET_OS_OSX
412*a1e26a70SApple OSS Distributions	T_SKIP("freezing is not available on macOS");
413*a1e26a70SApple OSS Distributions#endif /* TARGET_OS_OSX */
414*a1e26a70SApple OSS Distributions
415*a1e26a70SApple OSS Distributions	/* Try checking if freezing is enabled at all */
416*a1e26a70SApple OSS Distributions	if (sysctlbyname("vm.freeze_enabled", &freeze_enabled, &fe_len, NULL, 0) == -1) {
417*a1e26a70SApple OSS Distributions		if (errno == ENOENT) {
418*a1e26a70SApple OSS Distributions			T_SKIP("This device doesn't have CONFIG_FREEZE enabled.");
419*a1e26a70SApple OSS Distributions		} else {
420*a1e26a70SApple OSS Distributions			T_FAIL("failed to query vm.freeze_enabled, errno: %d", errno);
421*a1e26a70SApple OSS Distributions		}
422*a1e26a70SApple OSS Distributions	}
423*a1e26a70SApple OSS Distributions
424*a1e26a70SApple OSS Distributions	if (!freeze_enabled) {
425*a1e26a70SApple OSS Distributions		T_SKIP("Freeze is not enabled, skipping test.");
426*a1e26a70SApple OSS Distributions	}
427*a1e26a70SApple OSS Distributions
428*a1e26a70SApple OSS Distributions	/* signal handling */
429*a1e26a70SApple OSS Distributions	signal(SIGUSR1, SIG_IGN);
430*a1e26a70SApple OSS Distributions	child_sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dq);
431*a1e26a70SApple OSS Distributions	dispatch_source_set_event_handler(child_sig_src, ^{
432*a1e26a70SApple OSS Distributions		dispatch_semaphore_signal(child_done_sema);
433*a1e26a70SApple OSS Distributions	});
434*a1e26a70SApple OSS Distributions	dispatch_activate(child_sig_src);
435*a1e26a70SApple OSS Distributions
436*a1e26a70SApple OSS Distributions	T_ASSERT_POSIX_SUCCESS(dt_launch_tool(&child_pid, args, false, NULL, NULL), "child launched");
437*a1e26a70SApple OSS Distributions	T_ATEND(kill_children);
438*a1e26a70SApple OSS Distributions
439*a1e26a70SApple OSS Distributions	dispatch_semaphore_wait(child_done_sema, DISPATCH_TIME_FOREVER);
440*a1e26a70SApple OSS Distributions
441*a1e26a70SApple OSS Distributions	/* keep processes in memory */
442*a1e26a70SApple OSS Distributions	T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_to_memory", &oldftm, &oldlen, &newval, sizeof(newval)),
443*a1e26a70SApple OSS Distributions			"disabled freezing to disk");
444*a1e26a70SApple OSS Distributions
445*a1e26a70SApple OSS Distributions	/* set the ratio to zero */
446*a1e26a70SApple OSS Distributions	T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_private_shared_pages_ratio", &oldratio, &ratiolen, &newratio, sizeof(newratio)), "disabled private:shared ratio checking");
447*a1e26a70SApple OSS Distributions
448*a1e26a70SApple OSS Distributions	/* freeze the child */
449*a1e26a70SApple OSS Distributions	T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze", NULL, 0, &child_pid, sizeof(child_pid)),
450*a1e26a70SApple OSS Distributions			"froze child");
451*a1e26a70SApple OSS Distributions
452*a1e26a70SApple OSS Distributions	/* Sleep to allow the compressor to finish compressing the child */
453*a1e26a70SApple OSS Distributions	sleep(5);
454*a1e26a70SApple OSS Distributions
455*a1e26a70SApple OSS Distributions	/* take the stackshot and parse it */
456*a1e26a70SApple OSS Distributions	stackshot_config = take_stackshot(child_pid, STACKSHOT_ENABLE_BT_FAULTING | STACKSHOT_ENABLE_UUID_FAULTING, 0);
457*a1e26a70SApple OSS Distributions
458*a1e26a70SApple OSS Distributions	/* check that the stackshot has the stack frames */
459*a1e26a70SApple OSS Distributions	check_stackshot(stackshot_config, CHECK_FOR_FAULT_STATS | WRITE_STACKSHOT_BUFFER_TO_TMP);
460*a1e26a70SApple OSS Distributions
461*a1e26a70SApple OSS Distributions	T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_to_memory", NULL, 0, &oldftm, sizeof(oldftm)),
462*a1e26a70SApple OSS Distributions			"reset freezing to disk");
463*a1e26a70SApple OSS Distributions
464*a1e26a70SApple OSS Distributions	/* reset the private:shared ratio */
465*a1e26a70SApple OSS Distributions	T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_private_shared_pages_ratio", NULL, 0, &oldratio, sizeof(oldratio)), "reset private:shared ratio");
466*a1e26a70SApple OSS Distributions
467*a1e26a70SApple OSS Distributions	T_LOG("all done, killing child");
468*a1e26a70SApple OSS Distributions
469*a1e26a70SApple OSS Distributions	/* tell the child to quit */
470*a1e26a70SApple OSS Distributions	T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGTERM), "killed child");
471*a1e26a70SApple OSS Distributions}
472*a1e26a70SApple OSS Distributions
473*a1e26a70SApple OSS DistributionsT_DECL(fault_singleproc, "test that faulting stackshots work correctly in a single process setting", T_META_TAG_VM_PREFERRED)
474*a1e26a70SApple OSS Distributions{
475*a1e26a70SApple OSS Distributions	dispatch_semaphore_t child_done_sema = dispatch_semaphore_create(0);
476*a1e26a70SApple OSS Distributions	dispatch_queue_t dq = dispatch_queue_create("com.apple.stackshot_accuracy.fault_sp", NULL);
477*a1e26a70SApple OSS Distributions	void *stackshot_config;
478*a1e26a70SApple OSS Distributions	__block pthread_t child_thread;
479*a1e26a70SApple OSS Distributions	char *child_stack;
480*a1e26a70SApple OSS Distributions	size_t child_stacklen;
481*a1e26a70SApple OSS Distributions
482*a1e26a70SApple OSS Distributions#if !TARGET_OS_OSX
483*a1e26a70SApple OSS Distributions	T_SKIP("madvise(..., ..., MADV_PAGEOUT) is not available on embedded platforms");
484*a1e26a70SApple OSS Distributions#endif /* !TARGET_OS_OSX */
485*a1e26a70SApple OSS Distributions
486*a1e26a70SApple OSS Distributions	current_scenario_name = __func__;
487*a1e26a70SApple OSS Distributions
488*a1e26a70SApple OSS Distributions	dispatch_async(dq, ^{
489*a1e26a70SApple OSS Distributions		char padding[16 * 1024];
490*a1e26a70SApple OSS Distributions		__asm__ volatile(""::"r"(padding));
491*a1e26a70SApple OSS Distributions
492*a1e26a70SApple OSS Distributions		child_recurse(RECURSIONS, 0, ^{
493*a1e26a70SApple OSS Distributions			child_thread = pthread_self();
494*a1e26a70SApple OSS Distributions			dispatch_semaphore_signal(child_done_sema);
495*a1e26a70SApple OSS Distributions		});
496*a1e26a70SApple OSS Distributions	});
497*a1e26a70SApple OSS Distributions
498*a1e26a70SApple OSS Distributions	dispatch_semaphore_wait(child_done_sema, DISPATCH_TIME_FOREVER);
499*a1e26a70SApple OSS Distributions	T_LOG("done waiting for child");
500*a1e26a70SApple OSS Distributions
501*a1e26a70SApple OSS Distributions	child_stack = pthread_get_stackaddr_np(child_thread);
502*a1e26a70SApple OSS Distributions	child_stacklen = pthread_get_stacksize_np(child_thread);
503*a1e26a70SApple OSS Distributions	child_stack -= child_stacklen;
504*a1e26a70SApple OSS Distributions	T_LOG("child stack: [0x%p - 0x%p]: 0x%zu bytes", (void *)child_stack,
505*a1e26a70SApple OSS Distributions			(void *)(child_stack + child_stacklen), child_stacklen);
506*a1e26a70SApple OSS Distributions
507*a1e26a70SApple OSS Distributions	/* paging out the child */
508*a1e26a70SApple OSS Distributions	T_ASSERT_POSIX_SUCCESS(madvise(child_stack, child_stacklen, MADV_PAGEOUT), "paged out via madvise(2) the child stack");
509*a1e26a70SApple OSS Distributions
510*a1e26a70SApple OSS Distributions	/* take the stackshot and parse it */
511*a1e26a70SApple OSS Distributions	stackshot_config = take_stackshot(getpid(), STACKSHOT_ENABLE_BT_FAULTING | STACKSHOT_ENABLE_UUID_FAULTING, 0);
512*a1e26a70SApple OSS Distributions
513*a1e26a70SApple OSS Distributions	/* check that the stackshot has the stack frames */
514*a1e26a70SApple OSS Distributions	check_stackshot(stackshot_config, CHECK_FOR_FAULT_STATS | WRITE_STACKSHOT_BUFFER_TO_TMP);
515*a1e26a70SApple OSS Distributions
516*a1e26a70SApple OSS Distributions	T_LOG("done!");
517*a1e26a70SApple OSS Distributions}
518*a1e26a70SApple OSS Distributions
519*a1e26a70SApple OSS DistributionsT_DECL(zombie, "test that threads wedged in the kernel can be stackshot'd", T_META_TAG_VM_PREFERRED)
520*a1e26a70SApple OSS Distributions{
521*a1e26a70SApple OSS Distributions	dispatch_queue_t dq = dispatch_queue_create("com.apple.stackshot_accuracy.zombie", NULL);
522*a1e26a70SApple OSS Distributions	dispatch_semaphore_t child_done_sema = dispatch_semaphore_create(0);
523*a1e26a70SApple OSS Distributions	dispatch_source_t child_sig_src;
524*a1e26a70SApple OSS Distributions	void *stackshot_config;
525*a1e26a70SApple OSS Distributions	char path[PATH_MAX];
526*a1e26a70SApple OSS Distributions	uint32_t path_size = sizeof(path);
527*a1e26a70SApple OSS Distributions	char *args[] = { path, "-n", "sid_child_process", NULL };
528*a1e26a70SApple OSS Distributions
529*a1e26a70SApple OSS Distributions	current_scenario_name = __func__;
530*a1e26a70SApple OSS Distributions	T_QUIET; T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath");
531*a1e26a70SApple OSS Distributions
532*a1e26a70SApple OSS Distributions	T_LOG("parent pid: %d\n", getpid());
533*a1e26a70SApple OSS Distributions
534*a1e26a70SApple OSS Distributions	/* setup signal handling */
535*a1e26a70SApple OSS Distributions	signal(SIGUSR1, SIG_IGN);
536*a1e26a70SApple OSS Distributions	child_sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dq);
537*a1e26a70SApple OSS Distributions	dispatch_source_set_event_handler(child_sig_src, ^{
538*a1e26a70SApple OSS Distributions		dispatch_semaphore_signal(child_done_sema);
539*a1e26a70SApple OSS Distributions	});
540*a1e26a70SApple OSS Distributions	dispatch_activate(child_sig_src);
541*a1e26a70SApple OSS Distributions
542*a1e26a70SApple OSS Distributions	/* create the child process */
543*a1e26a70SApple OSS Distributions	T_ASSERT_POSIX_SUCCESS(dt_launch_tool(&child_pid, args, false, NULL, NULL), "child launched");
544*a1e26a70SApple OSS Distributions	T_ATEND(kill_children);
545*a1e26a70SApple OSS Distributions
546*a1e26a70SApple OSS Distributions	/* wait until the child has recursed enough */
547*a1e26a70SApple OSS Distributions	dispatch_semaphore_wait(child_done_sema, DISPATCH_TIME_FOREVER);
548*a1e26a70SApple OSS Distributions
549*a1e26a70SApple OSS Distributions	T_LOG("child finished, parent executing. invoking jetsam");
550*a1e26a70SApple OSS Distributions
551*a1e26a70SApple OSS Distributions	T_ASSERT_POSIX_SUCCESS(memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0),
552*a1e26a70SApple OSS Distributions			"jetsam'd the child");
553*a1e26a70SApple OSS Distributions
554*a1e26a70SApple OSS Distributions	/* Sleep to allow the target process to become zombified */
555*a1e26a70SApple OSS Distributions	sleep(1);
556*a1e26a70SApple OSS Distributions
557*a1e26a70SApple OSS Distributions	/* take the stackshot and parse it */
558*a1e26a70SApple OSS Distributions	stackshot_config = take_stackshot(child_pid, 0, 0);
559*a1e26a70SApple OSS Distributions
560*a1e26a70SApple OSS Distributions	/* check that the stackshot has the stack frames */
561*a1e26a70SApple OSS Distributions	check_stackshot(stackshot_config, CHECK_FOR_KERNEL_THREADS | WRITE_STACKSHOT_BUFFER_TO_TMP);
562*a1e26a70SApple OSS Distributions
563*a1e26a70SApple OSS Distributions	T_LOG("all done, unwedging and killing child");
564*a1e26a70SApple OSS Distributions
565*a1e26a70SApple OSS Distributions	int v = 1;
566*a1e26a70SApple OSS Distributions	T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.unwedge_thread", NULL, NULL, &v, sizeof(v)),
567*a1e26a70SApple OSS Distributions			"unwedged child");
568*a1e26a70SApple OSS Distributions
569*a1e26a70SApple OSS Distributions	/* tell the child to quit */
570*a1e26a70SApple OSS Distributions	T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGTERM), "killed child");
571*a1e26a70SApple OSS Distributions}
572