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