]>
Commit | Line | Data |
---|---|---|
d9a64523 A |
1 | #include <darwintest.h> |
2 | #include <darwintest_utils.h> | |
f427ee49 | 3 | #include <darwintest_multiprocess.h> |
d9a64523 A |
4 | #include <kern/debug.h> |
5 | #include <kern/kern_cdata.h> | |
ea3f0419 | 6 | #include <kern/block_hint.h> |
d9a64523 A |
7 | #include <kdd.h> |
8 | #include <libproc.h> | |
9 | #include <mach-o/dyld.h> | |
cb323159 | 10 | #include <mach-o/dyld_images.h> |
d9a64523 A |
11 | #include <mach-o/dyld_priv.h> |
12 | #include <sys/syscall.h> | |
13 | #include <sys/stackshot.h> | |
f427ee49 A |
14 | #include <uuid/uuid.h> |
15 | #include <servers/bootstrap.h> | |
16 | #include <pthread/workqueue_private.h> | |
c3c9b80d | 17 | #include <dispatch/private.h> |
f427ee49 | 18 | #import <zlib.h> |
d9a64523 | 19 | |
d9a64523 A |
20 | T_GLOBAL_META( |
21 | T_META_NAMESPACE("xnu.stackshot"), | |
22 | T_META_CHECK_LEAKS(false), | |
23 | T_META_ASROOT(true) | |
24 | ); | |
25 | ||
26 | static const char *current_process_name(void); | |
27 | static void verify_stackshot_sharedcache_layout(struct dyld_uuid_info_64 *uuids, uint32_t uuid_count); | |
f427ee49 | 28 | static void parse_stackshot(uint64_t stackshot_parsing_flags, void *ssbuf, size_t sslen, NSDictionary *extra); |
d9a64523 A |
29 | static void parse_thread_group_stackshot(void **sbuf, size_t sslen); |
30 | static uint64_t stackshot_timestamp(void *ssbuf, size_t sslen); | |
31 | static void initialize_thread(void); | |
32 | ||
f427ee49 A |
33 | static uint64_t global_flags = 0; |
34 | ||
d9a64523 A |
35 | #define DEFAULT_STACKSHOT_BUFFER_SIZE (1024 * 1024) |
36 | #define MAX_STACKSHOT_BUFFER_SIZE (6 * 1024 * 1024) | |
37 | ||
f427ee49 A |
38 | #define SRP_SERVICE_NAME "com.apple.xnu.test.stackshot.special_reply_port" |
39 | ||
d9a64523 | 40 | /* bit flags for parse_stackshot */ |
cb323159 A |
41 | #define PARSE_STACKSHOT_DELTA 0x01 |
42 | #define PARSE_STACKSHOT_ZOMBIE 0x02 | |
43 | #define PARSE_STACKSHOT_SHAREDCACHE_LAYOUT 0x04 | |
44 | #define PARSE_STACKSHOT_DISPATCH_QUEUE_LABEL 0x08 | |
45 | #define PARSE_STACKSHOT_TURNSTILEINFO 0x10 | |
f427ee49 | 46 | #define PARSE_STACKSHOT_POSTEXEC 0x20 |
ea3f0419 | 47 | #define PARSE_STACKSHOT_WAITINFO_CSEG 0x40 |
f427ee49 A |
48 | #define PARSE_STACKSHOT_WAITINFO_SRP 0x80 |
49 | #define PARSE_STACKSHOT_TRANSLATED 0x100 | |
c3c9b80d | 50 | #define PARSE_STACKSHOT_SHAREDCACHE_FLAGS 0x200 |
ea3f0419 | 51 | |
f427ee49 A |
52 | /* keys for 'extra' dictionary for parse_stackshot */ |
53 | static const NSString* zombie_child_pid_key = @"zombie_child_pid"; // -> @(pid), required for PARSE_STACKSHOT_ZOMBIE | |
54 | static const NSString* postexec_child_unique_pid_key = @"postexec_child_unique_pid"; // -> @(unique_pid), required for PARSE_STACKSHOT_POSTEXEC | |
55 | static const NSString* cseg_expected_threadid_key = @"cseg_expected_threadid"; // -> @(tid), required for PARSE_STACKSHOT_WAITINFO_CSEG | |
c3c9b80d A |
56 | static const NSString* srp_expected_threadid_key = @"srp_expected_threadid"; // -> @(tid), this or ..._pid required for PARSE_STACKSHOT_WAITINFO_SRP |
57 | static const NSString* srp_expected_pid_key = @"srp_expected_pid"; // -> @(pid), this or ..._threadid required for PARSE_STACKSHOT_WAITINFO_SRP | |
f427ee49 | 58 | static const NSString* translated_child_pid_key = @"translated_child_pid"; // -> @(pid), required for PARSE_STACKSHOT_TRANSLATED |
c3c9b80d A |
59 | static const NSString* sharedcache_child_pid_key = @"sharedcache_child_pid"; // @(pid), required for PARSE_STACKSHOT_SHAREDCACHE_FLAGS |
60 | static const NSString* sharedcache_child_sameaddr_key = @"sharedcache_child_sameaddr"; // @(0 or 1), required for PARSE_STACKSHOT_SHAREDCACHE_FLAGS | |
cb323159 A |
61 | |
62 | #define TEST_STACKSHOT_QUEUE_LABEL "houston.we.had.a.problem" | |
63 | #define TEST_STACKSHOT_QUEUE_LABEL_LENGTH sizeof(TEST_STACKSHOT_QUEUE_LABEL) | |
d9a64523 A |
64 | |
65 | T_DECL(microstackshots, "test the microstackshot syscall") | |
66 | { | |
67 | void *buf = NULL; | |
68 | unsigned int size = DEFAULT_STACKSHOT_BUFFER_SIZE; | |
69 | ||
70 | while (1) { | |
71 | buf = malloc(size); | |
72 | T_QUIET; T_ASSERT_NOTNULL(buf, "allocated stackshot buffer"); | |
73 | ||
74 | #pragma clang diagnostic push | |
75 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" | |
76 | int len = syscall(SYS_microstackshot, buf, size, | |
f427ee49 | 77 | (uint32_t) STACKSHOT_GET_MICROSTACKSHOT); |
d9a64523 A |
78 | #pragma clang diagnostic pop |
79 | if (len == ENOSYS) { | |
80 | T_SKIP("microstackshot syscall failed, likely not compiled with CONFIG_TELEMETRY"); | |
81 | } | |
82 | if (len == -1 && errno == ENOSPC) { | |
83 | /* syscall failed because buffer wasn't large enough, try again */ | |
84 | free(buf); | |
85 | buf = NULL; | |
86 | size *= 2; | |
87 | T_ASSERT_LE(size, (unsigned int)MAX_STACKSHOT_BUFFER_SIZE, | |
88 | "growing stackshot buffer to sane size"); | |
89 | continue; | |
90 | } | |
91 | T_ASSERT_POSIX_SUCCESS(len, "called microstackshot syscall"); | |
92 | break; | |
93 | } | |
94 | ||
95 | T_EXPECT_EQ(*(uint32_t *)buf, | |
96 | (uint32_t)STACKSHOT_MICRO_SNAPSHOT_MAGIC, | |
97 | "magic value for microstackshot matches"); | |
98 | ||
99 | free(buf); | |
100 | } | |
101 | ||
102 | struct scenario { | |
103 | const char *name; | |
f427ee49 | 104 | uint64_t flags; |
cb323159 | 105 | bool quiet; |
d9a64523 A |
106 | bool should_fail; |
107 | bool maybe_unsupported; | |
108 | pid_t target_pid; | |
109 | uint64_t since_timestamp; | |
110 | uint32_t size_hint; | |
111 | dt_stat_time_t timer; | |
112 | }; | |
113 | ||
114 | static void | |
115 | quiet(struct scenario *scenario) | |
116 | { | |
cb323159 | 117 | if (scenario->timer || scenario->quiet) { |
d9a64523 A |
118 | T_QUIET; |
119 | } | |
120 | } | |
121 | ||
122 | static void | |
f427ee49 | 123 | take_stackshot(struct scenario *scenario, bool compress_ok, void (^cb)(void *buf, size_t size)) |
d9a64523 | 124 | { |
f427ee49 | 125 | start: |
d9a64523 A |
126 | initialize_thread(); |
127 | ||
128 | void *config = stackshot_config_create(); | |
129 | quiet(scenario); | |
130 | T_ASSERT_NOTNULL(config, "created stackshot config"); | |
131 | ||
f427ee49 | 132 | int ret = stackshot_config_set_flags(config, scenario->flags | global_flags); |
d9a64523 | 133 | quiet(scenario); |
f427ee49 | 134 | T_ASSERT_POSIX_ZERO(ret, "set flags %#llx on stackshot config", scenario->flags); |
d9a64523 A |
135 | |
136 | if (scenario->size_hint > 0) { | |
137 | ret = stackshot_config_set_size_hint(config, scenario->size_hint); | |
138 | quiet(scenario); | |
139 | T_ASSERT_POSIX_ZERO(ret, "set size hint %" PRIu32 " on stackshot config", | |
140 | scenario->size_hint); | |
141 | } | |
142 | ||
143 | if (scenario->target_pid > 0) { | |
144 | ret = stackshot_config_set_pid(config, scenario->target_pid); | |
145 | quiet(scenario); | |
146 | T_ASSERT_POSIX_ZERO(ret, "set target pid %d on stackshot config", | |
147 | scenario->target_pid); | |
148 | } | |
149 | ||
150 | if (scenario->since_timestamp > 0) { | |
151 | ret = stackshot_config_set_delta_timestamp(config, scenario->since_timestamp); | |
152 | quiet(scenario); | |
153 | T_ASSERT_POSIX_ZERO(ret, "set since timestamp %" PRIu64 " on stackshot config", | |
154 | scenario->since_timestamp); | |
155 | } | |
156 | ||
157 | int retries_remaining = 5; | |
158 | ||
159 | retry: ; | |
160 | uint64_t start_time = mach_absolute_time(); | |
161 | ret = stackshot_capture_with_config(config); | |
162 | uint64_t end_time = mach_absolute_time(); | |
163 | ||
164 | if (scenario->should_fail) { | |
165 | T_EXPECTFAIL; | |
166 | T_ASSERT_POSIX_ZERO(ret, "called stackshot_capture_with_config"); | |
167 | return; | |
168 | } | |
169 | ||
170 | if (ret == EBUSY || ret == ETIMEDOUT) { | |
171 | if (retries_remaining > 0) { | |
172 | if (!scenario->timer) { | |
173 | T_LOG("stackshot_capture_with_config failed with %s (%d), retrying", | |
174 | strerror(ret), ret); | |
175 | } | |
176 | ||
177 | retries_remaining--; | |
178 | goto retry; | |
179 | } else { | |
180 | T_ASSERT_POSIX_ZERO(ret, | |
181 | "called stackshot_capture_with_config (no retries remaining)"); | |
182 | } | |
183 | } else if ((ret == ENOTSUP) && scenario->maybe_unsupported) { | |
184 | T_SKIP("kernel indicated this stackshot configuration is not supported"); | |
185 | } else { | |
186 | quiet(scenario); | |
187 | T_ASSERT_POSIX_ZERO(ret, "called stackshot_capture_with_config"); | |
188 | } | |
189 | ||
190 | if (scenario->timer) { | |
191 | dt_stat_mach_time_add(scenario->timer, end_time - start_time); | |
192 | } | |
193 | void *buf = stackshot_config_get_stackshot_buffer(config); | |
194 | size_t size = stackshot_config_get_stackshot_size(config); | |
195 | if (scenario->name) { | |
196 | char sspath[MAXPATHLEN]; | |
197 | strlcpy(sspath, scenario->name, sizeof(sspath)); | |
198 | strlcat(sspath, ".kcdata", sizeof(sspath)); | |
199 | T_QUIET; T_ASSERT_POSIX_ZERO(dt_resultfile(sspath, sizeof(sspath)), | |
200 | "create result file path"); | |
201 | ||
cb323159 A |
202 | if (!scenario->quiet) { |
203 | T_LOG("writing stackshot to %s", sspath); | |
204 | } | |
d9a64523 A |
205 | |
206 | FILE *f = fopen(sspath, "w"); | |
207 | T_WITH_ERRNO; T_QUIET; T_ASSERT_NOTNULL(f, | |
208 | "open stackshot output file"); | |
209 | ||
210 | size_t written = fwrite(buf, size, 1, f); | |
211 | T_QUIET; T_ASSERT_POSIX_SUCCESS(written, "wrote stackshot to file"); | |
212 | ||
213 | fclose(f); | |
214 | } | |
215 | cb(buf, size); | |
f427ee49 A |
216 | if (compress_ok) { |
217 | if (global_flags == 0) { | |
218 | T_LOG("Restarting test with compression"); | |
219 | global_flags |= STACKSHOT_DO_COMPRESS; | |
220 | goto start; | |
221 | } else { | |
222 | global_flags = 0; | |
223 | } | |
224 | } | |
d9a64523 A |
225 | |
226 | ret = stackshot_config_dealloc(config); | |
227 | T_QUIET; T_EXPECT_POSIX_ZERO(ret, "deallocated stackshot config"); | |
228 | } | |
229 | ||
f427ee49 A |
230 | T_DECL(simple_compressed, "take a simple compressed stackshot") |
231 | { | |
232 | struct scenario scenario = { | |
233 | .name = "kcdata_compressed", | |
234 | .flags = (STACKSHOT_DO_COMPRESS | STACKSHOT_SAVE_LOADINFO | STACKSHOT_THREAD_WAITINFO | STACKSHOT_GET_GLOBAL_MEM_STATS | | |
235 | STACKSHOT_SAVE_IMP_DONATION_PIDS | STACKSHOT_KCDATA_FORMAT), | |
236 | }; | |
237 | ||
238 | T_LOG("taking compressed kcdata stackshot"); | |
239 | take_stackshot(&scenario, true, ^(void *ssbuf, size_t sslen) { | |
240 | parse_stackshot(0, ssbuf, sslen, nil); | |
241 | }); | |
242 | } | |
243 | ||
244 | T_DECL(panic_compressed, "take a compressed stackshot with the same flags as a panic stackshot") | |
245 | { | |
246 | uint64_t stackshot_flags = (STACKSHOT_SAVE_KEXT_LOADINFO | | |
247 | STACKSHOT_SAVE_LOADINFO | | |
248 | STACKSHOT_KCDATA_FORMAT | | |
249 | STACKSHOT_ENABLE_BT_FAULTING | | |
250 | STACKSHOT_ENABLE_UUID_FAULTING | | |
251 | STACKSHOT_DO_COMPRESS | | |
252 | STACKSHOT_NO_IO_STATS | | |
253 | STACKSHOT_THREAD_WAITINFO | | |
254 | #if TARGET_OS_MAC | |
255 | STACKSHOT_COLLECT_SHAREDCACHE_LAYOUT | | |
256 | #endif | |
257 | STACKSHOT_DISABLE_LATENCY_INFO); | |
258 | ||
259 | struct scenario scenario = { | |
260 | .name = "kcdata_panic_compressed", | |
261 | .flags = stackshot_flags, | |
262 | }; | |
263 | ||
264 | T_LOG("taking compressed kcdata stackshot with panic flags"); | |
265 | take_stackshot(&scenario, true, ^(void *ssbuf, size_t sslen) { | |
266 | parse_stackshot(0, ssbuf, sslen, nil); | |
267 | }); | |
268 | } | |
269 | ||
d9a64523 A |
270 | T_DECL(kcdata, "test that kcdata stackshots can be taken and parsed") |
271 | { | |
272 | struct scenario scenario = { | |
273 | .name = "kcdata", | |
274 | .flags = (STACKSHOT_SAVE_LOADINFO | STACKSHOT_GET_GLOBAL_MEM_STATS | | |
275 | STACKSHOT_SAVE_IMP_DONATION_PIDS | STACKSHOT_KCDATA_FORMAT), | |
276 | }; | |
277 | ||
278 | T_LOG("taking kcdata stackshot"); | |
f427ee49 A |
279 | take_stackshot(&scenario, true, ^(void *ssbuf, size_t sslen) { |
280 | parse_stackshot(0, ssbuf, sslen, nil); | |
d9a64523 A |
281 | }); |
282 | } | |
283 | ||
284 | T_DECL(kcdata_faulting, "test that kcdata stackshots while faulting can be taken and parsed") | |
285 | { | |
286 | struct scenario scenario = { | |
287 | .name = "faulting", | |
288 | .flags = (STACKSHOT_SAVE_LOADINFO | STACKSHOT_GET_GLOBAL_MEM_STATS | |
289 | | STACKSHOT_SAVE_IMP_DONATION_PIDS | STACKSHOT_KCDATA_FORMAT | |
290 | | STACKSHOT_ENABLE_BT_FAULTING | STACKSHOT_ENABLE_UUID_FAULTING), | |
291 | }; | |
292 | ||
293 | T_LOG("taking faulting stackshot"); | |
f427ee49 A |
294 | take_stackshot(&scenario, true, ^(void *ssbuf, size_t sslen) { |
295 | parse_stackshot(0, ssbuf, sslen, nil); | |
d9a64523 A |
296 | }); |
297 | } | |
298 | ||
299 | T_DECL(bad_flags, "test a poorly-formed stackshot syscall") | |
300 | { | |
301 | struct scenario scenario = { | |
302 | .flags = STACKSHOT_SAVE_IN_KERNEL_BUFFER /* not allowed from user space */, | |
303 | .should_fail = true, | |
304 | }; | |
305 | ||
306 | T_LOG("attempting to take stackshot with kernel-only flag"); | |
f427ee49 | 307 | take_stackshot(&scenario, true, ^(__unused void *ssbuf, __unused size_t sslen) { |
d9a64523 A |
308 | T_ASSERT_FAIL("stackshot data callback called"); |
309 | }); | |
310 | } | |
311 | ||
312 | T_DECL(delta, "test delta stackshots") | |
313 | { | |
314 | struct scenario scenario = { | |
315 | .name = "delta", | |
316 | .flags = (STACKSHOT_SAVE_LOADINFO | STACKSHOT_GET_GLOBAL_MEM_STATS | |
317 | | STACKSHOT_SAVE_IMP_DONATION_PIDS | STACKSHOT_KCDATA_FORMAT), | |
318 | }; | |
319 | ||
320 | T_LOG("taking full stackshot"); | |
f427ee49 | 321 | take_stackshot(&scenario, false, ^(void *ssbuf, size_t sslen) { |
d9a64523 A |
322 | uint64_t stackshot_time = stackshot_timestamp(ssbuf, sslen); |
323 | ||
324 | T_LOG("taking delta stackshot since time %" PRIu64, stackshot_time); | |
325 | ||
f427ee49 | 326 | parse_stackshot(0, ssbuf, sslen, nil); |
d9a64523 A |
327 | |
328 | struct scenario delta_scenario = { | |
329 | .flags = (STACKSHOT_SAVE_LOADINFO | STACKSHOT_GET_GLOBAL_MEM_STATS | |
330 | | STACKSHOT_SAVE_IMP_DONATION_PIDS | STACKSHOT_KCDATA_FORMAT | |
331 | | STACKSHOT_COLLECT_DELTA_SNAPSHOT), | |
332 | .since_timestamp = stackshot_time | |
333 | }; | |
334 | ||
f427ee49 A |
335 | take_stackshot(&delta_scenario, false, ^(void *dssbuf, size_t dsslen) { |
336 | parse_stackshot(PARSE_STACKSHOT_DELTA, dssbuf, dsslen, nil); | |
d9a64523 A |
337 | }); |
338 | }); | |
339 | } | |
340 | ||
341 | T_DECL(shared_cache_layout, "test stackshot inclusion of shared cache layout") | |
342 | { | |
343 | struct scenario scenario = { | |
344 | .name = "shared_cache_layout", | |
345 | .flags = (STACKSHOT_SAVE_LOADINFO | STACKSHOT_GET_GLOBAL_MEM_STATS | |
346 | | STACKSHOT_SAVE_IMP_DONATION_PIDS | STACKSHOT_KCDATA_FORMAT | | |
347 | STACKSHOT_COLLECT_SHAREDCACHE_LAYOUT), | |
348 | }; | |
349 | ||
cb323159 A |
350 | size_t shared_cache_length; |
351 | const void *cache_header = _dyld_get_shared_cache_range(&shared_cache_length); | |
352 | if (cache_header == NULL) { | |
353 | T_SKIP("Device not running with shared cache, skipping test..."); | |
354 | } | |
355 | ||
356 | if (shared_cache_length == 0) { | |
357 | T_SKIP("dyld reports that currently running shared cache has zero length"); | |
358 | } | |
359 | ||
d9a64523 | 360 | T_LOG("taking stackshot with STACKSHOT_COLLECT_SHAREDCACHE_LAYOUT set"); |
f427ee49 A |
361 | take_stackshot(&scenario, true, ^(void *ssbuf, size_t sslen) { |
362 | parse_stackshot(PARSE_STACKSHOT_SHAREDCACHE_LAYOUT, ssbuf, sslen, nil); | |
d9a64523 A |
363 | }); |
364 | } | |
365 | ||
cb323159 A |
366 | T_DECL(stress, "test that taking stackshots for 60 seconds doesn't crash the system") |
367 | { | |
368 | uint64_t max_diff_time = 60ULL /* seconds */ * 1000000000ULL; | |
369 | uint64_t start_time; | |
370 | ||
371 | struct scenario scenario = { | |
372 | .name = "stress", | |
373 | .quiet = true, | |
374 | .flags = (STACKSHOT_KCDATA_FORMAT | | |
375 | STACKSHOT_THREAD_WAITINFO | | |
376 | STACKSHOT_SAVE_LOADINFO | | |
377 | STACKSHOT_SAVE_KEXT_LOADINFO | | |
378 | STACKSHOT_GET_GLOBAL_MEM_STATS | | |
cb323159 A |
379 | STACKSHOT_SAVE_IMP_DONATION_PIDS | |
380 | STACKSHOT_COLLECT_SHAREDCACHE_LAYOUT | | |
381 | STACKSHOT_THREAD_GROUP | | |
382 | STACKSHOT_SAVE_JETSAM_COALITIONS | | |
383 | STACKSHOT_ASID | | |
cb323159 A |
384 | 0), |
385 | }; | |
386 | ||
387 | start_time = clock_gettime_nsec_np(CLOCK_MONOTONIC); | |
388 | while (clock_gettime_nsec_np(CLOCK_MONOTONIC) - start_time < max_diff_time) { | |
c3c9b80d A |
389 | take_stackshot(&scenario, false, ^(void * __unused ssbuf, |
390 | size_t __unused sslen) { | |
cb323159 A |
391 | printf("."); |
392 | fflush(stdout); | |
393 | }); | |
394 | ||
395 | /* Leave some time for the testing infrastructure to catch up */ | |
396 | usleep(10000); | |
397 | ||
398 | } | |
399 | printf("\n"); | |
400 | } | |
401 | ||
402 | T_DECL(dispatch_queue_label, "test that kcdata stackshots contain libdispatch queue labels") | |
403 | { | |
404 | struct scenario scenario = { | |
405 | .name = "kcdata", | |
406 | .flags = (STACKSHOT_GET_DQ | STACKSHOT_KCDATA_FORMAT), | |
407 | }; | |
408 | dispatch_semaphore_t child_ready_sem, parent_done_sem; | |
409 | dispatch_queue_t dq; | |
410 | ||
411 | #if TARGET_OS_WATCH | |
412 | T_SKIP("This test is flaky on watches: 51663346"); | |
413 | #endif | |
414 | ||
415 | child_ready_sem = dispatch_semaphore_create(0); | |
416 | T_QUIET; T_ASSERT_NOTNULL(child_ready_sem, "dqlabel child semaphore"); | |
417 | ||
418 | parent_done_sem = dispatch_semaphore_create(0); | |
419 | T_QUIET; T_ASSERT_NOTNULL(parent_done_sem, "dqlabel parent semaphore"); | |
420 | ||
421 | dq = dispatch_queue_create(TEST_STACKSHOT_QUEUE_LABEL, NULL); | |
422 | T_QUIET; T_ASSERT_NOTNULL(dq, "dispatch queue"); | |
423 | ||
424 | /* start the helper thread */ | |
425 | dispatch_async(dq, ^{ | |
426 | dispatch_semaphore_signal(child_ready_sem); | |
427 | ||
428 | dispatch_semaphore_wait(parent_done_sem, DISPATCH_TIME_FOREVER); | |
429 | }); | |
430 | ||
431 | /* block behind the child starting up */ | |
432 | dispatch_semaphore_wait(child_ready_sem, DISPATCH_TIME_FOREVER); | |
433 | ||
434 | T_LOG("taking kcdata stackshot with libdispatch queue labels"); | |
f427ee49 A |
435 | take_stackshot(&scenario, true, ^(void *ssbuf, size_t sslen) { |
436 | parse_stackshot(PARSE_STACKSHOT_DISPATCH_QUEUE_LABEL, ssbuf, sslen, nil); | |
cb323159 A |
437 | }); |
438 | ||
439 | dispatch_semaphore_signal(parent_done_sem); | |
440 | } | |
441 | ||
c3c9b80d A |
442 | #define CACHEADDR_ENV "STACKSHOT_TEST_DYLDADDR" |
443 | T_HELPER_DECL(spawn_reslide_child, "child process to spawn with alternate slide") | |
444 | { | |
445 | size_t shared_cache_len; | |
446 | const void *addr, *prevaddr; | |
447 | uintmax_t v; | |
448 | char *endptr; | |
449 | ||
450 | const char *cacheaddr_env = getenv(CACHEADDR_ENV); | |
451 | T_QUIET; T_ASSERT_NOTNULL(cacheaddr_env, "getenv("CACHEADDR_ENV")"); | |
452 | errno = 0; | |
453 | endptr = NULL; | |
454 | v = strtoumax(cacheaddr_env, &endptr, 16); /* read hex value */ | |
455 | T_WITH_ERRNO; T_QUIET; T_ASSERT_NE(v, 0l, "getenv(%s) = \"%s\" should be a non-zero hex number", CACHEADDR_ENV, cacheaddr_env); | |
456 | T_QUIET; T_ASSERT_EQ(*endptr, 0, "getenv(%s) = \"%s\" endptr \"%s\" should be empty", CACHEADDR_ENV, cacheaddr_env, endptr); | |
457 | ||
458 | prevaddr = (const void *)v; | |
459 | addr = _dyld_get_shared_cache_range(&shared_cache_len); | |
460 | T_QUIET; T_ASSERT_NOTNULL(addr, "shared cache address"); | |
461 | ||
462 | T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(getppid(), (addr == prevaddr) ? SIGUSR2 : SIGUSR1), "signaled parent to take stackshot"); | |
463 | for (;;) { | |
464 | (void) pause(); /* parent will kill -9 us */ | |
465 | } | |
466 | } | |
467 | ||
468 | T_DECL(shared_cache_flags, "tests stackshot's task_ss_flags for the shared cache") | |
469 | { | |
470 | posix_spawnattr_t attr; | |
471 | char *env_addr; | |
472 | char path[PATH_MAX]; | |
473 | __block bool child_same_addr = false; | |
474 | ||
475 | uint32_t path_size = sizeof(path); | |
476 | T_QUIET; T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath"); | |
477 | char *args[] = { path, "-n", "spawn_reslide_child", NULL }; | |
478 | pid_t pid; | |
479 | size_t shared_cache_len; | |
480 | const void *addr; | |
481 | ||
482 | dispatch_source_t child_diffsig_src, child_samesig_src; | |
483 | dispatch_semaphore_t child_ready_sem = dispatch_semaphore_create(0); | |
484 | T_QUIET; T_ASSERT_NOTNULL(child_ready_sem, "shared_cache child semaphore"); | |
485 | ||
486 | dispatch_queue_t signal_processing_q = dispatch_queue_create("signal processing queue", NULL); | |
487 | T_QUIET; T_ASSERT_NOTNULL(signal_processing_q, "signal processing queue"); | |
488 | ||
489 | signal(SIGUSR1, SIG_IGN); | |
490 | signal(SIGUSR2, SIG_IGN); | |
491 | child_samesig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, signal_processing_q); | |
492 | T_QUIET; T_ASSERT_NOTNULL(child_samesig_src, "dispatch_source_create (child_samesig_src)"); | |
493 | child_diffsig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR2, 0, signal_processing_q); | |
494 | T_QUIET; T_ASSERT_NOTNULL(child_diffsig_src, "dispatch_source_create (child_diffsig_src)"); | |
495 | ||
496 | /* child will signal us depending on if their addr is the same or different */ | |
497 | dispatch_source_set_event_handler(child_samesig_src, ^{ child_same_addr = false; dispatch_semaphore_signal(child_ready_sem); }); | |
498 | dispatch_source_set_event_handler(child_diffsig_src, ^{ child_same_addr = true; dispatch_semaphore_signal(child_ready_sem); }); | |
499 | dispatch_activate(child_samesig_src); | |
500 | dispatch_activate(child_diffsig_src); | |
501 | ||
502 | addr = _dyld_get_shared_cache_range(&shared_cache_len); | |
503 | T_QUIET; T_ASSERT_NOTNULL(addr, "shared cache address"); | |
504 | ||
505 | T_QUIET; T_ASSERT_POSIX_SUCCESS(asprintf(&env_addr, "%p", addr), "asprintf of env_addr succeeded"); | |
506 | T_QUIET; T_ASSERT_POSIX_SUCCESS(setenv(CACHEADDR_ENV, env_addr, true), "setting "CACHEADDR_ENV" to %s", env_addr); | |
507 | ||
508 | T_QUIET; T_ASSERT_POSIX_ZERO(posix_spawnattr_init(&attr), "posix_spawnattr_init"); | |
509 | T_QUIET; T_ASSERT_POSIX_ZERO(posix_spawnattr_setflags(&attr, _POSIX_SPAWN_RESLIDE), "posix_spawnattr_setflags"); | |
510 | int sp_ret = posix_spawn(&pid, path, NULL, &attr, args, environ); | |
511 | T_ASSERT_POSIX_ZERO(sp_ret, "spawned process '%s' with PID %d", args[0], pid); | |
512 | ||
513 | dispatch_semaphore_wait(child_ready_sem, DISPATCH_TIME_FOREVER); | |
514 | T_LOG("received signal from child (%s), capturing stackshot", child_same_addr ? "same shared cache addr" : "different shared cache addr"); | |
515 | ||
516 | struct scenario scenario = { | |
517 | .name = "shared_cache_flags", | |
518 | .flags = (STACKSHOT_SAVE_LOADINFO | STACKSHOT_GET_GLOBAL_MEM_STATS | |
519 | | STACKSHOT_COLLECT_SHAREDCACHE_LAYOUT | |
520 | | STACKSHOT_SAVE_IMP_DONATION_PIDS | STACKSHOT_KCDATA_FORMAT), | |
521 | }; | |
522 | ||
523 | take_stackshot(&scenario, false, ^( void *ssbuf, size_t sslen) { | |
524 | int status; | |
525 | /* First kill the child so we can reap it */ | |
526 | T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(pid, SIGKILL), "killing spawned process"); | |
527 | T_QUIET; T_ASSERT_POSIX_SUCCESS(waitpid(pid, &status, 0), "waitpid on spawned child"); | |
528 | T_QUIET; T_ASSERT_EQ(!!WIFSIGNALED(status), 1, "waitpid status should be signalled"); | |
529 | T_QUIET; T_ASSERT_EQ(WTERMSIG(status), SIGKILL, "waitpid status should be SIGKILLed"); | |
530 | ||
531 | parse_stackshot(PARSE_STACKSHOT_SHAREDCACHE_FLAGS, ssbuf, sslen, | |
532 | @{sharedcache_child_pid_key: @(pid), sharedcache_child_sameaddr_key: @(child_same_addr ? 1 : 0)}); | |
533 | }); | |
534 | } | |
535 | ||
d9a64523 A |
536 | static void *stuck_sysctl_thread(void *arg) { |
537 | int val = 1; | |
538 | dispatch_semaphore_t child_thread_started = *(dispatch_semaphore_t *)arg; | |
539 | ||
540 | dispatch_semaphore_signal(child_thread_started); | |
541 | T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.wedge_thread", NULL, NULL, &val, sizeof(val)), "wedge child thread"); | |
542 | ||
543 | return NULL; | |
544 | } | |
545 | ||
546 | T_HELPER_DECL(zombie_child, "child process to sample as a zombie") | |
547 | { | |
548 | pthread_t pthread; | |
549 | dispatch_semaphore_t child_thread_started = dispatch_semaphore_create(0); | |
550 | T_QUIET; T_ASSERT_NOTNULL(child_thread_started, "zombie child thread semaphore"); | |
551 | ||
552 | /* spawn another thread to get stuck in the kernel, then call exit() to become a zombie */ | |
553 | T_QUIET; T_ASSERT_POSIX_SUCCESS(pthread_create(&pthread, NULL, stuck_sysctl_thread, &child_thread_started), "pthread_create"); | |
554 | ||
555 | dispatch_semaphore_wait(child_thread_started, DISPATCH_TIME_FOREVER); | |
556 | ||
557 | /* sleep for a bit in the hope of ensuring that the other thread has called the sysctl before we signal the parent */ | |
558 | usleep(100); | |
559 | T_ASSERT_POSIX_SUCCESS(kill(getppid(), SIGUSR1), "signaled parent to take stackshot"); | |
560 | ||
561 | exit(0); | |
562 | } | |
563 | ||
564 | T_DECL(zombie, "tests a stackshot of a zombie task with a thread stuck in the kernel") | |
565 | { | |
566 | char path[PATH_MAX]; | |
567 | uint32_t path_size = sizeof(path); | |
568 | T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath"); | |
569 | char *args[] = { path, "-n", "zombie_child", NULL }; | |
570 | ||
571 | dispatch_source_t child_sig_src; | |
572 | dispatch_semaphore_t child_ready_sem = dispatch_semaphore_create(0); | |
573 | T_QUIET; T_ASSERT_NOTNULL(child_ready_sem, "zombie child semaphore"); | |
574 | ||
575 | dispatch_queue_t signal_processing_q = dispatch_queue_create("signal processing queue", NULL); | |
cb323159 | 576 | T_QUIET; T_ASSERT_NOTNULL(signal_processing_q, "signal processing queue"); |
d9a64523 A |
577 | |
578 | pid_t pid; | |
579 | ||
580 | T_LOG("spawning a child"); | |
581 | ||
582 | signal(SIGUSR1, SIG_IGN); | |
583 | child_sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, signal_processing_q); | |
584 | T_QUIET; T_ASSERT_NOTNULL(child_sig_src, "dispatch_source_create (child_sig_src)"); | |
585 | ||
586 | dispatch_source_set_event_handler(child_sig_src, ^{ dispatch_semaphore_signal(child_ready_sem); }); | |
587 | dispatch_activate(child_sig_src); | |
588 | ||
589 | int sp_ret = posix_spawn(&pid, args[0], NULL, NULL, args, NULL); | |
590 | T_QUIET; T_ASSERT_POSIX_ZERO(sp_ret, "spawned process '%s' with PID %d", args[0], pid); | |
591 | ||
592 | dispatch_semaphore_wait(child_ready_sem, DISPATCH_TIME_FOREVER); | |
593 | ||
594 | T_LOG("received signal from child, capturing stackshot"); | |
595 | ||
596 | struct proc_bsdshortinfo bsdshortinfo; | |
597 | int retval, iterations_to_wait = 10; | |
598 | ||
599 | while (iterations_to_wait > 0) { | |
600 | retval = proc_pidinfo(pid, PROC_PIDT_SHORTBSDINFO, 0, &bsdshortinfo, sizeof(bsdshortinfo)); | |
601 | if ((retval == 0) && errno == ESRCH) { | |
602 | T_LOG("unable to find child using proc_pidinfo, assuming zombie"); | |
603 | break; | |
604 | } | |
605 | ||
606 | T_QUIET; T_WITH_ERRNO; T_ASSERT_GT(retval, 0, "proc_pidinfo(PROC_PIDT_SHORTBSDINFO) returned a value > 0"); | |
607 | T_QUIET; T_ASSERT_EQ(retval, (int)sizeof(bsdshortinfo), "proc_pidinfo call for PROC_PIDT_SHORTBSDINFO returned expected size"); | |
608 | ||
609 | if (bsdshortinfo.pbsi_flags & PROC_FLAG_INEXIT) { | |
610 | T_LOG("child proc info marked as in exit"); | |
611 | break; | |
612 | } | |
613 | ||
614 | iterations_to_wait--; | |
615 | if (iterations_to_wait == 0) { | |
616 | /* | |
617 | * This will mark the test as failed but let it continue so we | |
618 | * don't leave a process stuck in the kernel. | |
619 | */ | |
620 | T_FAIL("unable to discover that child is marked as exiting"); | |
621 | } | |
622 | ||
623 | /* Give the child a few more seconds to make it to exit */ | |
624 | sleep(5); | |
625 | } | |
626 | ||
627 | /* Give the child some more time to make it through exit */ | |
628 | sleep(10); | |
629 | ||
630 | struct scenario scenario = { | |
631 | .name = "zombie", | |
632 | .flags = (STACKSHOT_SAVE_LOADINFO | STACKSHOT_GET_GLOBAL_MEM_STATS | |
633 | | STACKSHOT_SAVE_IMP_DONATION_PIDS | STACKSHOT_KCDATA_FORMAT), | |
634 | }; | |
635 | ||
f427ee49 | 636 | take_stackshot(&scenario, false, ^( void *ssbuf, size_t sslen) { |
d9a64523 A |
637 | /* First unwedge the child so we can reap it */ |
638 | int val = 1, status; | |
639 | T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.unwedge_thread", NULL, NULL, &val, sizeof(val)), "unwedge child"); | |
640 | ||
641 | T_QUIET; T_ASSERT_POSIX_SUCCESS(waitpid(pid, &status, 0), "waitpid on zombie child"); | |
642 | ||
f427ee49 A |
643 | parse_stackshot(PARSE_STACKSHOT_ZOMBIE, ssbuf, sslen, @{zombie_child_pid_key: @(pid)}); |
644 | }); | |
645 | } | |
646 | ||
647 | T_HELPER_DECL(exec_child_preexec, "child process pre-exec") | |
648 | { | |
649 | dispatch_queue_t signal_processing_q = dispatch_queue_create("signal processing queue", NULL); | |
650 | T_QUIET; T_ASSERT_NOTNULL(signal_processing_q, "signal processing queue"); | |
651 | ||
652 | signal(SIGUSR1, SIG_IGN); | |
653 | dispatch_source_t parent_sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, signal_processing_q); | |
654 | T_QUIET; T_ASSERT_NOTNULL(parent_sig_src, "dispatch_source_create (child_sig_src)"); | |
655 | dispatch_source_set_event_handler(parent_sig_src, ^{ | |
656 | ||
657 | // Parent took a timestamp then signaled us: exec into the next process | |
658 | ||
659 | char path[PATH_MAX]; | |
660 | uint32_t path_size = sizeof(path); | |
661 | T_QUIET; T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath"); | |
662 | char *args[] = { path, "-n", "exec_child_postexec", NULL }; | |
663 | ||
664 | T_QUIET; T_ASSERT_POSIX_ZERO(execve(args[0], args, NULL), "execing into exec_child_postexec"); | |
665 | }); | |
666 | dispatch_activate(parent_sig_src); | |
667 | ||
668 | T_ASSERT_POSIX_SUCCESS(kill(getppid(), SIGUSR1), "signaled parent to take timestamp"); | |
669 | ||
670 | sleep(100); | |
671 | // Should never get here | |
672 | T_FAIL("Received signal to exec from parent"); | |
673 | } | |
674 | ||
675 | T_HELPER_DECL(exec_child_postexec, "child process post-exec to sample") | |
676 | { | |
677 | T_ASSERT_POSIX_SUCCESS(kill(getppid(), SIGUSR1), "signaled parent to take stackshot"); | |
678 | sleep(100); | |
679 | // Should never get here | |
680 | T_FAIL("Killed by parent"); | |
681 | } | |
682 | ||
683 | T_DECL(exec, "test getting full task snapshots for a task that execs") | |
684 | { | |
685 | char path[PATH_MAX]; | |
686 | uint32_t path_size = sizeof(path); | |
687 | T_QUIET; T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath"); | |
688 | char *args[] = { path, "-n", "exec_child_preexec", NULL }; | |
689 | ||
690 | dispatch_source_t child_sig_src; | |
691 | dispatch_semaphore_t child_ready_sem = dispatch_semaphore_create(0); | |
692 | T_QUIET; T_ASSERT_NOTNULL(child_ready_sem, "exec child semaphore"); | |
693 | ||
694 | dispatch_queue_t signal_processing_q = dispatch_queue_create("signal processing queue", NULL); | |
695 | T_QUIET; T_ASSERT_NOTNULL(signal_processing_q, "signal processing queue"); | |
696 | ||
697 | pid_t pid; | |
698 | ||
699 | T_LOG("spawning a child"); | |
700 | ||
701 | signal(SIGUSR1, SIG_IGN); | |
702 | child_sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, signal_processing_q); | |
703 | T_QUIET; T_ASSERT_NOTNULL(child_sig_src, "dispatch_source_create (child_sig_src)"); | |
704 | ||
705 | dispatch_source_set_event_handler(child_sig_src, ^{ dispatch_semaphore_signal(child_ready_sem); }); | |
706 | dispatch_activate(child_sig_src); | |
707 | ||
708 | int sp_ret = posix_spawn(&pid, args[0], NULL, NULL, args, NULL); | |
709 | T_QUIET; T_ASSERT_POSIX_ZERO(sp_ret, "spawned process '%s' with PID %d", args[0], pid); | |
710 | ||
711 | dispatch_semaphore_wait(child_ready_sem, DISPATCH_TIME_FOREVER); | |
712 | ||
713 | uint64_t start_time = mach_absolute_time(); | |
714 | ||
715 | struct proc_uniqidentifierinfo proc_info_data = { }; | |
716 | int retval = proc_pidinfo(getpid(), PROC_PIDUNIQIDENTIFIERINFO, 0, &proc_info_data, sizeof(proc_info_data)); | |
717 | T_QUIET; T_EXPECT_POSIX_SUCCESS(retval, "proc_pidinfo PROC_PIDUNIQIDENTIFIERINFO"); | |
718 | T_QUIET; T_ASSERT_EQ_INT(retval, (int) sizeof(proc_info_data), "proc_pidinfo PROC_PIDUNIQIDENTIFIERINFO returned data"); | |
719 | uint64_t unique_pid = proc_info_data.p_uniqueid; | |
720 | ||
721 | T_LOG("received signal from pre-exec child, unique_pid is %llu, timestamp is %llu", unique_pid, start_time); | |
722 | ||
723 | T_ASSERT_POSIX_SUCCESS(kill(pid, SIGUSR1), "signaled pre-exec child to exec"); | |
724 | ||
725 | dispatch_semaphore_wait(child_ready_sem, DISPATCH_TIME_FOREVER); | |
726 | ||
727 | T_LOG("received signal from post-exec child, capturing stackshot"); | |
728 | ||
729 | struct scenario scenario = { | |
730 | .name = "exec", | |
731 | .flags = (STACKSHOT_SAVE_LOADINFO | STACKSHOT_GET_GLOBAL_MEM_STATS | |
732 | | STACKSHOT_SAVE_IMP_DONATION_PIDS | STACKSHOT_KCDATA_FORMAT | |
733 | | STACKSHOT_COLLECT_DELTA_SNAPSHOT), | |
734 | .since_timestamp = start_time | |
735 | }; | |
736 | ||
737 | take_stackshot(&scenario, false, ^( void *ssbuf, size_t sslen) { | |
738 | // Kill the child | |
739 | int status; | |
740 | T_ASSERT_POSIX_SUCCESS(kill(pid, SIGKILL), "kill post-exec child %d", pid); | |
741 | T_ASSERT_POSIX_SUCCESS(waitpid(pid, &status, 0), "waitpid on post-exec child"); | |
742 | ||
743 | parse_stackshot(PARSE_STACKSHOT_POSTEXEC | PARSE_STACKSHOT_DELTA, ssbuf, sslen, @{postexec_child_unique_pid_key: @(unique_pid)}); | |
d9a64523 A |
744 | }); |
745 | } | |
746 | ||
cb323159 A |
747 | static uint32_t |
748 | get_user_promotion_basepri(void) | |
749 | { | |
750 | mach_msg_type_number_t count = THREAD_POLICY_STATE_COUNT; | |
751 | struct thread_policy_state thread_policy; | |
752 | boolean_t get_default = FALSE; | |
753 | mach_port_t thread_port = pthread_mach_thread_np(pthread_self()); | |
754 | ||
755 | kern_return_t kr = thread_policy_get(thread_port, THREAD_POLICY_STATE, | |
756 | (thread_policy_t)&thread_policy, &count, &get_default); | |
757 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "thread_policy_get"); | |
758 | return thread_policy.thps_user_promotion_basepri; | |
759 | } | |
760 | ||
761 | static int | |
762 | get_pri(thread_t thread_port) | |
763 | { | |
764 | kern_return_t kr; | |
765 | ||
766 | thread_extended_info_data_t extended_info; | |
767 | mach_msg_type_number_t count = THREAD_EXTENDED_INFO_COUNT; | |
768 | kr = thread_info(thread_port, THREAD_EXTENDED_INFO, | |
769 | (thread_info_t)&extended_info, &count); | |
770 | ||
771 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "thread_info"); | |
772 | ||
773 | return extended_info.pth_curpri; | |
774 | } | |
775 | ||
776 | ||
777 | T_DECL(turnstile_singlehop, "turnstile single hop test") | |
778 | { | |
779 | dispatch_queue_t dq1, dq2; | |
780 | dispatch_semaphore_t sema_x; | |
781 | dispatch_queue_attr_t dq1_attr, dq2_attr; | |
f427ee49 A |
782 | __block qos_class_t main_qos = 0; |
783 | __block int main_relpri = 0, main_relpri2 = 0, main_afterpri = 0; | |
cb323159 A |
784 | struct scenario scenario = { |
785 | .name = "turnstile_singlehop", | |
786 | .flags = (STACKSHOT_THREAD_WAITINFO | STACKSHOT_KCDATA_FORMAT), | |
787 | }; | |
788 | dq1_attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0); | |
789 | dq2_attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0); | |
790 | pthread_mutex_t lock_a = PTHREAD_MUTEX_INITIALIZER; | |
791 | pthread_mutex_t lock_b = PTHREAD_MUTEX_INITIALIZER; | |
792 | ||
793 | pthread_mutex_t *lockap = &lock_a, *lockbp = &lock_b; | |
794 | ||
795 | dq1 = dispatch_queue_create("q1", dq1_attr); | |
796 | dq2 = dispatch_queue_create("q2", dq2_attr); | |
797 | sema_x = dispatch_semaphore_create(0); | |
798 | ||
799 | pthread_mutex_lock(lockap); | |
800 | dispatch_async(dq1, ^{ | |
801 | pthread_mutex_lock(lockbp); | |
802 | T_ASSERT_POSIX_SUCCESS(pthread_get_qos_class_np(pthread_self(), &main_qos, &main_relpri), "get qos class"); | |
803 | T_LOG("The priority of q1 is %d\n", get_pri(mach_thread_self())); | |
804 | dispatch_semaphore_signal(sema_x); | |
805 | pthread_mutex_lock(lockap); | |
806 | }); | |
807 | dispatch_semaphore_wait(sema_x, DISPATCH_TIME_FOREVER); | |
808 | ||
809 | T_LOG("Async1 completed"); | |
810 | ||
811 | pthread_set_qos_class_self_np(QOS_CLASS_UTILITY, 0); | |
812 | T_ASSERT_POSIX_SUCCESS(pthread_get_qos_class_np(pthread_self(), &main_qos, &main_relpri), "get qos class"); | |
813 | T_LOG("The priority of main is %d\n", get_pri(mach_thread_self())); | |
814 | main_relpri = get_pri(mach_thread_self()); | |
815 | ||
816 | dispatch_async(dq2, ^{ | |
817 | T_ASSERT_POSIX_SUCCESS(pthread_get_qos_class_np(pthread_self(), &main_qos, &main_relpri2), "get qos class"); | |
818 | T_LOG("The priority of q2 is %d\n", get_pri(mach_thread_self())); | |
819 | dispatch_semaphore_signal(sema_x); | |
820 | pthread_mutex_lock(lockbp); | |
821 | }); | |
822 | dispatch_semaphore_wait(sema_x, DISPATCH_TIME_FOREVER); | |
823 | ||
824 | T_LOG("Async2 completed"); | |
825 | ||
826 | while (1) { | |
f427ee49 | 827 | main_afterpri = (int) get_user_promotion_basepri(); |
cb323159 A |
828 | if (main_relpri != main_afterpri) { |
829 | T_LOG("Success with promotion pri is %d", main_afterpri); | |
830 | break; | |
831 | } | |
832 | ||
833 | usleep(100); | |
834 | } | |
835 | ||
f427ee49 A |
836 | take_stackshot(&scenario, true, ^( void *ssbuf, size_t sslen) { |
837 | parse_stackshot(PARSE_STACKSHOT_TURNSTILEINFO, ssbuf, sslen, nil); | |
cb323159 A |
838 | }); |
839 | } | |
840 | ||
841 | ||
d9a64523 A |
842 | static void |
843 | expect_instrs_cycles_in_stackshot(void *ssbuf, size_t sslen) | |
844 | { | |
845 | kcdata_iter_t iter = kcdata_iter(ssbuf, sslen); | |
846 | ||
847 | bool in_task = false; | |
848 | bool in_thread = false; | |
849 | bool saw_instrs_cycles = false; | |
850 | iter = kcdata_iter_next(iter); | |
851 | ||
852 | KCDATA_ITER_FOREACH(iter) { | |
853 | switch (kcdata_iter_type(iter)) { | |
854 | case KCDATA_TYPE_CONTAINER_BEGIN: | |
855 | switch (kcdata_iter_container_type(iter)) { | |
856 | case STACKSHOT_KCCONTAINER_TASK: | |
857 | in_task = true; | |
858 | saw_instrs_cycles = false; | |
859 | break; | |
860 | ||
861 | case STACKSHOT_KCCONTAINER_THREAD: | |
862 | in_thread = true; | |
863 | saw_instrs_cycles = false; | |
864 | break; | |
865 | ||
866 | default: | |
867 | break; | |
868 | } | |
869 | break; | |
870 | ||
871 | case STACKSHOT_KCTYPE_INSTRS_CYCLES: | |
872 | saw_instrs_cycles = true; | |
873 | break; | |
874 | ||
875 | case KCDATA_TYPE_CONTAINER_END: | |
876 | if (in_thread) { | |
877 | T_QUIET; T_EXPECT_TRUE(saw_instrs_cycles, | |
878 | "saw instructions and cycles in thread"); | |
879 | in_thread = false; | |
880 | } else if (in_task) { | |
881 | T_QUIET; T_EXPECT_TRUE(saw_instrs_cycles, | |
882 | "saw instructions and cycles in task"); | |
883 | in_task = false; | |
884 | } | |
885 | ||
886 | default: | |
887 | break; | |
888 | } | |
889 | } | |
890 | } | |
891 | ||
892 | static void | |
893 | skip_if_monotonic_unsupported(void) | |
894 | { | |
895 | int supported = 0; | |
896 | size_t supported_size = sizeof(supported); | |
897 | int ret = sysctlbyname("kern.monotonic.supported", &supported, | |
898 | &supported_size, 0, 0); | |
899 | if (ret < 0 || !supported) { | |
900 | T_SKIP("monotonic is unsupported"); | |
901 | } | |
902 | } | |
903 | ||
904 | T_DECL(instrs_cycles, "test a getting instructions and cycles in stackshot") | |
905 | { | |
906 | skip_if_monotonic_unsupported(); | |
907 | ||
908 | struct scenario scenario = { | |
909 | .name = "instrs-cycles", | |
910 | .flags = (STACKSHOT_SAVE_LOADINFO | STACKSHOT_INSTRS_CYCLES | |
911 | | STACKSHOT_KCDATA_FORMAT), | |
912 | }; | |
913 | ||
914 | T_LOG("attempting to take stackshot with instructions and cycles"); | |
f427ee49 A |
915 | take_stackshot(&scenario, false, ^(void *ssbuf, size_t sslen) { |
916 | parse_stackshot(0, ssbuf, sslen, nil); | |
d9a64523 A |
917 | expect_instrs_cycles_in_stackshot(ssbuf, sslen); |
918 | }); | |
919 | } | |
920 | ||
921 | T_DECL(delta_instrs_cycles, | |
922 | "test delta stackshots with instructions and cycles") | |
923 | { | |
924 | skip_if_monotonic_unsupported(); | |
925 | ||
926 | struct scenario scenario = { | |
927 | .name = "delta-instrs-cycles", | |
928 | .flags = (STACKSHOT_SAVE_LOADINFO | STACKSHOT_INSTRS_CYCLES | |
929 | | STACKSHOT_KCDATA_FORMAT), | |
930 | }; | |
931 | ||
932 | T_LOG("taking full stackshot"); | |
f427ee49 | 933 | take_stackshot(&scenario, false, ^(void *ssbuf, size_t sslen) { |
d9a64523 A |
934 | uint64_t stackshot_time = stackshot_timestamp(ssbuf, sslen); |
935 | ||
936 | T_LOG("taking delta stackshot since time %" PRIu64, stackshot_time); | |
937 | ||
f427ee49 | 938 | parse_stackshot(0, ssbuf, sslen, nil); |
d9a64523 A |
939 | expect_instrs_cycles_in_stackshot(ssbuf, sslen); |
940 | ||
941 | struct scenario delta_scenario = { | |
942 | .name = "delta-instrs-cycles-next", | |
943 | .flags = (STACKSHOT_SAVE_LOADINFO | STACKSHOT_INSTRS_CYCLES | |
944 | | STACKSHOT_KCDATA_FORMAT | |
945 | | STACKSHOT_COLLECT_DELTA_SNAPSHOT), | |
946 | .since_timestamp = stackshot_time, | |
947 | }; | |
948 | ||
f427ee49 A |
949 | take_stackshot(&delta_scenario, false, ^(void *dssbuf, size_t dsslen) { |
950 | parse_stackshot(PARSE_STACKSHOT_DELTA, dssbuf, dsslen, nil); | |
d9a64523 A |
951 | expect_instrs_cycles_in_stackshot(dssbuf, dsslen); |
952 | }); | |
953 | }); | |
954 | } | |
955 | ||
956 | static void | |
957 | check_thread_groups_supported() | |
958 | { | |
959 | int err; | |
960 | int supported = 0; | |
961 | size_t supported_size = sizeof(supported); | |
962 | err = sysctlbyname("kern.thread_groups_supported", &supported, &supported_size, NULL, 0); | |
963 | ||
964 | if (err || !supported) | |
965 | T_SKIP("thread groups not supported on this system"); | |
966 | } | |
967 | ||
968 | T_DECL(thread_groups, "test getting thread groups in stackshot") | |
969 | { | |
970 | check_thread_groups_supported(); | |
971 | ||
972 | struct scenario scenario = { | |
973 | .name = "thread-groups", | |
974 | .flags = (STACKSHOT_SAVE_LOADINFO | STACKSHOT_THREAD_GROUP | |
975 | | STACKSHOT_KCDATA_FORMAT), | |
976 | }; | |
977 | ||
978 | T_LOG("attempting to take stackshot with thread group flag"); | |
f427ee49 | 979 | take_stackshot(&scenario, false, ^(void *ssbuf, size_t sslen) { |
d9a64523 A |
980 | parse_thread_group_stackshot(ssbuf, sslen); |
981 | }); | |
982 | } | |
983 | ||
984 | static void | |
985 | parse_page_table_asid_stackshot(void **ssbuf, size_t sslen) | |
986 | { | |
987 | bool seen_asid = false; | |
988 | bool seen_page_table_snapshot = false; | |
989 | kcdata_iter_t iter = kcdata_iter(ssbuf, sslen); | |
990 | T_ASSERT_EQ(kcdata_iter_type(iter), KCDATA_BUFFER_BEGIN_STACKSHOT, | |
991 | "buffer provided is a stackshot"); | |
992 | ||
993 | iter = kcdata_iter_next(iter); | |
994 | KCDATA_ITER_FOREACH(iter) { | |
995 | switch (kcdata_iter_type(iter)) { | |
996 | case KCDATA_TYPE_ARRAY: { | |
997 | T_QUIET; | |
998 | T_ASSERT_TRUE(kcdata_iter_array_valid(iter), | |
999 | "checked that array is valid"); | |
1000 | ||
1001 | if (kcdata_iter_array_elem_type(iter) != STACKSHOT_KCTYPE_PAGE_TABLES) { | |
1002 | continue; | |
1003 | } | |
1004 | ||
1005 | T_ASSERT_FALSE(seen_page_table_snapshot, "check that we haven't yet seen a page table snapshot"); | |
1006 | seen_page_table_snapshot = true; | |
1007 | ||
1008 | T_ASSERT_EQ((size_t) kcdata_iter_array_elem_size(iter), sizeof(uint64_t), | |
1009 | "check that each element of the pagetable dump is the expected size"); | |
1010 | ||
1011 | uint64_t *pt_array = kcdata_iter_payload(iter); | |
1012 | uint32_t elem_count = kcdata_iter_array_elem_count(iter); | |
1013 | uint32_t j; | |
1014 | bool nonzero_tte = false; | |
1015 | for (j = 0; j < elem_count;) { | |
1016 | T_QUIET; T_ASSERT_LE(j + 4, elem_count, "check for valid page table segment header"); | |
1017 | uint64_t pa = pt_array[j]; | |
1018 | uint64_t num_entries = pt_array[j + 1]; | |
1019 | uint64_t start_va = pt_array[j + 2]; | |
1020 | uint64_t end_va = pt_array[j + 3]; | |
1021 | ||
1022 | T_QUIET; T_ASSERT_NE(pa, (uint64_t) 0, "check that the pagetable physical address is non-zero"); | |
1023 | T_QUIET; T_ASSERT_EQ(pa % (num_entries * sizeof(uint64_t)), (uint64_t) 0, "check that the pagetable physical address is correctly aligned"); | |
1024 | T_QUIET; T_ASSERT_NE(num_entries, (uint64_t) 0, "check that a pagetable region has more than 0 entries"); | |
1025 | T_QUIET; T_ASSERT_LE(j + 4 + num_entries, (uint64_t) elem_count, "check for sufficient space in page table array"); | |
1026 | T_QUIET; T_ASSERT_GT(end_va, start_va, "check for valid VA bounds in page table segment header"); | |
1027 | ||
1028 | for (uint32_t k = j + 4; k < (j + 4 + num_entries); ++k) { | |
1029 | if (pt_array[k] != 0) { | |
1030 | nonzero_tte = true; | |
1031 | T_QUIET; T_ASSERT_EQ((pt_array[k] >> 48) & 0xf, (uint64_t) 0, "check that bits[48:51] of arm64 TTE are clear"); | |
1032 | // L0-L2 table and non-compressed L3 block entries should always have bit 1 set; assumes L0-L2 blocks will not be used outside the kernel | |
1033 | bool table = ((pt_array[k] & 0x2) != 0); | |
1034 | if (table) { | |
1035 | T_QUIET; T_ASSERT_NE(pt_array[k] & ((1ULL << 48) - 1) & ~((1ULL << 12) - 1), (uint64_t) 0, "check that arm64 TTE physical address is non-zero"); | |
1036 | } else { // should be a compressed PTE | |
1037 | T_QUIET; T_ASSERT_NE(pt_array[k] & 0xC000000000000000ULL, (uint64_t) 0, "check that compressed PTE has at least one of bits [63:62] set"); | |
1038 | T_QUIET; T_ASSERT_EQ(pt_array[k] & ~0xC000000000000000ULL, (uint64_t) 0, "check that compressed PTE has no other bits besides [63:62] set"); | |
1039 | } | |
1040 | } | |
1041 | } | |
1042 | ||
1043 | j += (4 + num_entries); | |
1044 | } | |
1045 | T_ASSERT_TRUE(nonzero_tte, "check that we saw at least one non-empty TTE"); | |
1046 | T_ASSERT_EQ(j, elem_count, "check that page table dump size matches extent of last header"); | |
1047 | break; | |
1048 | } | |
1049 | case STACKSHOT_KCTYPE_ASID: { | |
1050 | T_ASSERT_FALSE(seen_asid, "check that we haven't yet seen an ASID"); | |
1051 | seen_asid = true; | |
1052 | } | |
1053 | } | |
1054 | } | |
1055 | T_ASSERT_TRUE(seen_page_table_snapshot, "check that we have seen a page table snapshot"); | |
1056 | T_ASSERT_TRUE(seen_asid, "check that we have seen an ASID"); | |
1057 | } | |
1058 | ||
1059 | T_DECL(dump_page_tables, "test stackshot page table dumping support") | |
1060 | { | |
1061 | struct scenario scenario = { | |
1062 | .name = "asid-page-tables", | |
1063 | .flags = (STACKSHOT_KCDATA_FORMAT | STACKSHOT_ASID | STACKSHOT_PAGE_TABLES), | |
1064 | .size_hint = (1ULL << 23), // 8 MB | |
1065 | .target_pid = getpid(), | |
1066 | .maybe_unsupported = true, | |
1067 | }; | |
1068 | ||
1069 | T_LOG("attempting to take stackshot with ASID and page table flags"); | |
f427ee49 | 1070 | take_stackshot(&scenario, false, ^(void *ssbuf, size_t sslen) { |
d9a64523 A |
1071 | parse_page_table_asid_stackshot(ssbuf, sslen); |
1072 | }); | |
1073 | } | |
1074 | ||
cb323159 A |
1075 | static void stackshot_verify_current_proc_uuid_info(void **ssbuf, size_t sslen, uint64_t expected_offset, const struct proc_uniqidentifierinfo *proc_info_data) |
1076 | { | |
1077 | const uuid_t *current_uuid = (const uuid_t *)(&proc_info_data->p_uuid); | |
1078 | ||
1079 | kcdata_iter_t iter = kcdata_iter(ssbuf, sslen); | |
1080 | T_ASSERT_EQ(kcdata_iter_type(iter), KCDATA_BUFFER_BEGIN_STACKSHOT, "buffer provided is a stackshot"); | |
1081 | ||
1082 | iter = kcdata_iter_next(iter); | |
1083 | ||
1084 | KCDATA_ITER_FOREACH(iter) { | |
1085 | switch (kcdata_iter_type(iter)) { | |
1086 | case KCDATA_TYPE_ARRAY: { | |
1087 | T_QUIET; T_ASSERT_TRUE(kcdata_iter_array_valid(iter), "checked that array is valid"); | |
1088 | if (kcdata_iter_array_elem_type(iter) == KCDATA_TYPE_LIBRARY_LOADINFO64) { | |
1089 | struct user64_dyld_uuid_info *info = (struct user64_dyld_uuid_info *) kcdata_iter_payload(iter); | |
1090 | if (uuid_compare(*current_uuid, info->imageUUID) == 0) { | |
1091 | T_ASSERT_EQ(expected_offset, info->imageLoadAddress, "found matching UUID with matching binary offset"); | |
1092 | return; | |
1093 | } | |
1094 | } else if (kcdata_iter_array_elem_type(iter) == KCDATA_TYPE_LIBRARY_LOADINFO) { | |
1095 | struct user32_dyld_uuid_info *info = (struct user32_dyld_uuid_info *) kcdata_iter_payload(iter); | |
1096 | if (uuid_compare(*current_uuid, info->imageUUID) == 0) { | |
1097 | T_ASSERT_EQ(expected_offset, ((uint64_t) info->imageLoadAddress), "found matching UUID with matching binary offset"); | |
1098 | return; | |
1099 | } | |
1100 | } | |
1101 | break; | |
1102 | } | |
1103 | default: | |
1104 | break; | |
1105 | } | |
1106 | } | |
1107 | ||
1108 | T_FAIL("failed to find matching UUID in stackshot data"); | |
1109 | } | |
1110 | ||
f427ee49 A |
1111 | T_DECL(translated, "tests translated bit is set correctly") |
1112 | { | |
1113 | #if !(TARGET_OS_OSX && TARGET_CPU_ARM64) | |
c3c9b80d | 1114 | T_SKIP("Only valid on Apple silicon Macs") |
f427ee49 A |
1115 | #endif |
1116 | // Get path of stackshot_translated_child helper binary | |
1117 | char path[PATH_MAX]; | |
1118 | uint32_t path_size = sizeof(path); | |
1119 | T_QUIET; T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath"); | |
1120 | char* binary_name = strrchr(path, '/'); | |
1121 | if (binary_name) binary_name++; | |
1122 | T_QUIET; T_ASSERT_NOTNULL(binary_name, "Find basename in path '%s'", path); | |
1123 | strlcpy(binary_name, "stackshot_translated_child", path_size - (binary_name - path)); | |
1124 | char *args[] = { path, NULL }; | |
1125 | ||
1126 | dispatch_source_t child_sig_src; | |
1127 | dispatch_semaphore_t child_ready_sem = dispatch_semaphore_create(0); | |
1128 | T_QUIET; T_ASSERT_NOTNULL(child_ready_sem, "exec child semaphore"); | |
1129 | ||
1130 | dispatch_queue_t signal_processing_q = dispatch_queue_create("signal processing queue", NULL); | |
1131 | T_QUIET; T_ASSERT_NOTNULL(signal_processing_q, "signal processing queue"); | |
1132 | ||
1133 | signal(SIGUSR1, SIG_IGN); | |
1134 | child_sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, signal_processing_q); | |
1135 | T_QUIET; T_ASSERT_NOTNULL(child_sig_src, "dispatch_source_create (child_sig_src)"); | |
1136 | ||
1137 | dispatch_source_set_event_handler(child_sig_src, ^{ dispatch_semaphore_signal(child_ready_sem); }); | |
1138 | dispatch_activate(child_sig_src); | |
1139 | ||
1140 | // Spawn child | |
1141 | pid_t pid; | |
1142 | T_LOG("spawning translated child"); | |
1143 | T_QUIET; T_ASSERT_POSIX_ZERO(posix_spawn(&pid, args[0], NULL, NULL, args, NULL), "spawned process '%s' with PID %d", args[0], pid); | |
1144 | ||
1145 | // Wait for the the child to spawn up | |
1146 | dispatch_semaphore_wait(child_ready_sem, DISPATCH_TIME_FOREVER); | |
1147 | ||
1148 | // Make sure the child is running and is translated | |
1149 | int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }; | |
1150 | struct kinfo_proc process_info; | |
1151 | size_t bufsize = sizeof(process_info); | |
1152 | T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctl(mib, (unsigned)(sizeof(mib)/sizeof(int)), &process_info, &bufsize, NULL, 0), "get translated child process info"); | |
c3c9b80d | 1153 | T_QUIET; T_ASSERT_GT(bufsize, (size_t)0, "process info is not empty"); |
f427ee49 A |
1154 | T_QUIET; T_ASSERT_TRUE((process_info.kp_proc.p_flag & P_TRANSLATED), "KERN_PROC_PID reports child is translated"); |
1155 | ||
1156 | T_LOG("capturing stackshot"); | |
1157 | ||
1158 | struct scenario scenario = { | |
1159 | .name = "translated", | |
1160 | .flags = (STACKSHOT_SAVE_LOADINFO | STACKSHOT_GET_GLOBAL_MEM_STATS | |
1161 | | STACKSHOT_SAVE_IMP_DONATION_PIDS | STACKSHOT_KCDATA_FORMAT), | |
1162 | }; | |
1163 | ||
1164 | take_stackshot(&scenario, true, ^( void *ssbuf, size_t sslen) { | |
f427ee49 A |
1165 | parse_stackshot(PARSE_STACKSHOT_TRANSLATED, ssbuf, sslen, @{translated_child_pid_key: @(pid)}); |
1166 | }); | |
c3c9b80d A |
1167 | |
1168 | // Kill the child | |
1169 | int status; | |
1170 | T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(pid, SIGTERM), "kill translated child"); | |
1171 | T_QUIET; T_ASSERT_POSIX_SUCCESS(waitpid(pid, &status, 0), "waitpid on translated child"); | |
1172 | ||
f427ee49 A |
1173 | } |
1174 | ||
cb323159 A |
1175 | T_DECL(proc_uuid_info, "tests that the main binary UUID for a proc is always populated") |
1176 | { | |
1177 | struct proc_uniqidentifierinfo proc_info_data = { }; | |
1178 | mach_msg_type_number_t count; | |
1179 | kern_return_t kernel_status; | |
1180 | task_dyld_info_data_t task_dyld_info; | |
1181 | struct dyld_all_image_infos *target_infos; | |
1182 | int retval; | |
1183 | bool found_image_in_image_infos = false; | |
1184 | uint64_t expected_mach_header_offset = 0; | |
1185 | ||
1186 | /* Find the UUID of our main binary */ | |
1187 | retval = proc_pidinfo(getpid(), PROC_PIDUNIQIDENTIFIERINFO, 0, &proc_info_data, sizeof(proc_info_data)); | |
1188 | T_QUIET; T_EXPECT_POSIX_SUCCESS(retval, "proc_pidinfo PROC_PIDUNIQIDENTIFIERINFO"); | |
1189 | T_QUIET; T_ASSERT_EQ_INT(retval, (int) sizeof(proc_info_data), "proc_pidinfo PROC_PIDUNIQIDENTIFIERINFO returned data"); | |
1190 | ||
1191 | uuid_string_t str = {}; | |
1192 | uuid_unparse(*(uuid_t*)&proc_info_data.p_uuid, str); | |
1193 | T_LOG("Found current UUID is %s", str); | |
1194 | ||
1195 | /* Find the location of the dyld image info metadata */ | |
1196 | count = TASK_DYLD_INFO_COUNT; | |
1197 | kernel_status = task_info(mach_task_self(), TASK_DYLD_INFO, (task_info_t)&task_dyld_info, &count); | |
1198 | T_QUIET; T_ASSERT_EQ(kernel_status, KERN_SUCCESS, "retrieve task_info for TASK_DYLD_INFO"); | |
1199 | ||
1200 | target_infos = (struct dyld_all_image_infos *)task_dyld_info.all_image_info_addr; | |
1201 | ||
1202 | /* Find our binary in the dyld image info array */ | |
1203 | for (int i = 0; i < (int) target_infos->uuidArrayCount; i++) { | |
1204 | if (uuid_compare(target_infos->uuidArray[i].imageUUID, *(uuid_t*)&proc_info_data.p_uuid) == 0) { | |
1205 | expected_mach_header_offset = (uint64_t) target_infos->uuidArray[i].imageLoadAddress; | |
1206 | found_image_in_image_infos = true; | |
1207 | } | |
1208 | } | |
1209 | ||
1210 | T_ASSERT_TRUE(found_image_in_image_infos, "found binary image in dyld image info list"); | |
1211 | ||
1212 | /* Overwrite the dyld image info data so the kernel has to fallback to the UUID stored in the proc structure */ | |
1213 | target_infos->uuidArrayCount = 0; | |
1214 | ||
1215 | struct scenario scenario = { | |
1216 | .name = "proc_uuid_info", | |
1217 | .flags = (STACKSHOT_SAVE_LOADINFO | STACKSHOT_KCDATA_FORMAT), | |
1218 | .target_pid = getpid(), | |
1219 | }; | |
1220 | ||
1221 | T_LOG("attempting to take stackshot for current PID"); | |
f427ee49 | 1222 | take_stackshot(&scenario, false, ^(void *ssbuf, size_t sslen) { |
cb323159 A |
1223 | stackshot_verify_current_proc_uuid_info(ssbuf, sslen, expected_mach_header_offset, &proc_info_data); |
1224 | }); | |
1225 | } | |
1226 | ||
ea3f0419 A |
1227 | T_DECL(cseg_waitinfo, "test that threads stuck in the compressor report correct waitinfo") |
1228 | { | |
ea3f0419 A |
1229 | struct scenario scenario = { |
1230 | .name = "cseg_waitinfo", | |
1231 | .quiet = false, | |
1232 | .flags = (STACKSHOT_THREAD_WAITINFO | STACKSHOT_KCDATA_FORMAT), | |
1233 | }; | |
f427ee49 | 1234 | __block uint64_t thread_id = 0; |
ea3f0419 A |
1235 | |
1236 | dispatch_queue_t dq = dispatch_queue_create("com.apple.stackshot.cseg_waitinfo", NULL); | |
1237 | dispatch_semaphore_t child_ok = dispatch_semaphore_create(0); | |
1238 | ||
1239 | dispatch_async(dq, ^{ | |
f427ee49 | 1240 | pthread_threadid_np(NULL, &thread_id); |
ea3f0419 | 1241 | dispatch_semaphore_signal(child_ok); |
c3c9b80d | 1242 | int val = 1; |
ea3f0419 A |
1243 | T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.cseg_wedge_thread", NULL, NULL, &val, sizeof(val)), "wedge child thread"); |
1244 | }); | |
1245 | ||
1246 | dispatch_semaphore_wait(child_ok, DISPATCH_TIME_FOREVER); | |
1247 | sleep(1); | |
1248 | ||
1249 | T_LOG("taking stackshot"); | |
f427ee49 | 1250 | take_stackshot(&scenario, false, ^(void *ssbuf, size_t sslen) { |
c3c9b80d | 1251 | int val = 1; |
ea3f0419 | 1252 | T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.cseg_unwedge_thread", NULL, NULL, &val, sizeof(val)), "unwedge child thread"); |
f427ee49 A |
1253 | parse_stackshot(PARSE_STACKSHOT_WAITINFO_CSEG, ssbuf, sslen, @{cseg_expected_threadid_key: @(thread_id)}); |
1254 | }); | |
1255 | } | |
1256 | ||
1257 | static void | |
1258 | srp_send( | |
1259 | mach_port_t send_port, | |
1260 | mach_port_t reply_port, | |
1261 | mach_port_t msg_port) | |
1262 | { | |
1263 | kern_return_t ret = 0; | |
1264 | ||
1265 | struct test_msg { | |
1266 | mach_msg_header_t header; | |
1267 | mach_msg_body_t body; | |
1268 | mach_msg_port_descriptor_t port_descriptor; | |
1269 | }; | |
1270 | struct test_msg send_msg = { | |
1271 | .header = { | |
1272 | .msgh_remote_port = send_port, | |
1273 | .msgh_local_port = reply_port, | |
1274 | .msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, | |
1275 | reply_port ? MACH_MSG_TYPE_MAKE_SEND_ONCE : 0, | |
1276 | MACH_MSG_TYPE_MOVE_SEND, | |
1277 | MACH_MSGH_BITS_COMPLEX), | |
1278 | .msgh_id = 0x100, | |
1279 | .msgh_size = sizeof(send_msg), | |
1280 | }, | |
1281 | .body = { | |
1282 | .msgh_descriptor_count = 1, | |
1283 | }, | |
1284 | .port_descriptor = { | |
1285 | .name = msg_port, | |
1286 | .disposition = MACH_MSG_TYPE_MOVE_RECEIVE, | |
1287 | .type = MACH_MSG_PORT_DESCRIPTOR, | |
1288 | }, | |
1289 | }; | |
1290 | ||
1291 | if (msg_port == MACH_PORT_NULL) { | |
1292 | send_msg.body.msgh_descriptor_count = 0; | |
1293 | } | |
1294 | ||
1295 | ret = mach_msg(&(send_msg.header), | |
1296 | MACH_SEND_MSG | | |
1297 | MACH_SEND_TIMEOUT | | |
1298 | MACH_SEND_OVERRIDE | | |
1299 | (reply_port ? MACH_SEND_SYNC_OVERRIDE : 0), | |
1300 | send_msg.header.msgh_size, | |
1301 | 0, | |
1302 | MACH_PORT_NULL, | |
1303 | 10000, | |
1304 | 0); | |
1305 | ||
1306 | T_ASSERT_MACH_SUCCESS(ret, "client mach_msg"); | |
1307 | } | |
1308 | ||
1309 | T_HELPER_DECL(srp_client, | |
1310 | "Client used for the special_reply_port test") | |
1311 | { | |
1312 | pid_t ppid = getppid(); | |
1313 | dispatch_semaphore_t can_continue = dispatch_semaphore_create(0); | |
1314 | dispatch_queue_t dq = dispatch_queue_create("client_signalqueue", NULL); | |
1315 | dispatch_source_t sig_src; | |
1316 | ||
1317 | mach_msg_return_t mr; | |
1318 | mach_port_t service_port; | |
1319 | mach_port_t conn_port; | |
1320 | mach_port_t special_reply_port; | |
1321 | mach_port_options_t opts = { | |
1322 | .flags = MPO_INSERT_SEND_RIGHT, | |
1323 | }; | |
1324 | ||
1325 | signal(SIGUSR1, SIG_IGN); | |
1326 | sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dq); | |
1327 | ||
1328 | dispatch_source_set_event_handler(sig_src, ^{ | |
1329 | dispatch_semaphore_signal(can_continue); | |
1330 | }); | |
1331 | dispatch_activate(sig_src); | |
1332 | ||
1333 | /* lookup the mach service port for the parent */ | |
1334 | kern_return_t kr = bootstrap_look_up(bootstrap_port, | |
1335 | SRP_SERVICE_NAME, &service_port); | |
1336 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "client bootstrap_look_up"); | |
1337 | ||
1338 | /* create the send-once right (special reply port) and message to send to the server */ | |
1339 | kr = mach_port_construct(mach_task_self(), &opts, 0ull, &conn_port); | |
1340 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_construct"); | |
1341 | ||
1342 | special_reply_port = thread_get_special_reply_port(); | |
1343 | T_QUIET; T_ASSERT_TRUE(MACH_PORT_VALID(special_reply_port), "get_thread_special_reply_port"); | |
1344 | ||
1345 | /* send the message with the special reply port */ | |
1346 | srp_send(service_port, special_reply_port, conn_port); | |
1347 | ||
1348 | /* signal the parent to continue */ | |
1349 | kill(ppid, SIGUSR1); | |
1350 | ||
1351 | struct { | |
1352 | mach_msg_header_t header; | |
1353 | mach_msg_body_t body; | |
1354 | mach_msg_port_descriptor_t port_descriptor; | |
1355 | } rcv_msg = { | |
1356 | .header = | |
1357 | { | |
1358 | .msgh_remote_port = MACH_PORT_NULL, | |
1359 | .msgh_local_port = special_reply_port, | |
1360 | .msgh_size = sizeof(rcv_msg), | |
1361 | }, | |
1362 | }; | |
1363 | ||
1364 | /* wait on the reply from the parent (that we will never receive) */ | |
1365 | mr = mach_msg(&(rcv_msg.header), | |
1366 | (MACH_RCV_MSG | MACH_RCV_SYNC_WAIT), | |
1367 | 0, | |
1368 | rcv_msg.header.msgh_size, | |
1369 | special_reply_port, | |
1370 | MACH_MSG_TIMEOUT_NONE, | |
1371 | service_port); | |
1372 | ||
1373 | /* not expected to execute as parent will SIGKILL client... */ | |
1374 | T_LOG("client process exiting after sending message to parent (server)"); | |
1375 | } | |
1376 | ||
c3c9b80d A |
1377 | enum srp_test_type { |
1378 | SRP_TEST_THREAD, /* expect waiter on current thread */ | |
1379 | SRP_TEST_PID, /* expect waiter on current PID */ | |
1380 | SRP_TEST_EITHER, /* waiter could be on either */ | |
1381 | }; | |
1382 | ||
1383 | static void | |
1384 | check_srp_test(const char *name, enum srp_test_type ty) | |
1385 | { | |
1386 | struct scenario scenario = { | |
1387 | .name = name, | |
1388 | .quiet = false, | |
1389 | .flags = (STACKSHOT_THREAD_WAITINFO | STACKSHOT_KCDATA_FORMAT), | |
1390 | }; | |
1391 | uint64_t thread_id = 0; | |
1392 | pthread_threadid_np(NULL, &thread_id); | |
1393 | if (ty == SRP_TEST_THREAD) { | |
1394 | take_stackshot(&scenario, false, ^(void *ssbuf, size_t sslen) { | |
1395 | parse_stackshot(PARSE_STACKSHOT_WAITINFO_SRP, ssbuf, sslen, | |
1396 | @{srp_expected_threadid_key: @(thread_id)}); | |
1397 | }); | |
1398 | } else if (ty == SRP_TEST_PID) { | |
1399 | take_stackshot(&scenario, false, ^(void *ssbuf, size_t sslen) { | |
1400 | parse_stackshot(PARSE_STACKSHOT_WAITINFO_SRP, ssbuf, sslen, | |
1401 | @{srp_expected_pid_key: @(getpid())}); | |
1402 | }); | |
1403 | } else { | |
1404 | take_stackshot(&scenario, false, ^(void *ssbuf, size_t sslen) { | |
1405 | parse_stackshot(PARSE_STACKSHOT_WAITINFO_SRP, ssbuf, sslen, | |
1406 | @{srp_expected_pid_key: @(getpid()), srp_expected_threadid_key: @(thread_id)}); | |
1407 | }); | |
1408 | } | |
1409 | ||
1410 | } | |
1411 | ||
1412 | ||
f427ee49 A |
1413 | /* |
1414 | * Tests the stackshot wait info plumbing for synchronous IPC that doesn't use kevent on the server. | |
1415 | * | |
1416 | * (part 1): tests the scenario where a client sends a request that includes a special reply port | |
1417 | * to a server that doesn't receive the message and doesn't copy the send-once right | |
1418 | * into its address space as a result. for this case the special reply port is enqueued | |
1419 | * in a port and we check which task has that receive right and use that info. (rdar://60440338) | |
1420 | * (part 2): tests the scenario where a client sends a request that includes a special reply port | |
1421 | * to a server that receives the message and copies in the send-once right, but doesn't | |
1422 | * reply to the client. for this case the special reply port is copied out and the kernel | |
1423 | * stashes the info about which task copied out the send once right. (rdar://60440592) | |
c3c9b80d A |
1424 | * (part 3): tests the same as part 2, but uses kevents, which allow for |
1425 | * priority inheritance | |
f427ee49 A |
1426 | */ |
1427 | T_DECL(special_reply_port, "test that tasks using special reply ports have correct waitinfo") | |
1428 | { | |
1429 | dispatch_semaphore_t can_continue = dispatch_semaphore_create(0); | |
1430 | dispatch_queue_t dq = dispatch_queue_create("signalqueue", NULL); | |
c3c9b80d | 1431 | dispatch_queue_t machdq = dispatch_queue_create("machqueue", NULL); |
f427ee49 A |
1432 | dispatch_source_t sig_src; |
1433 | char path[PATH_MAX]; | |
1434 | uint32_t path_size = sizeof(path); | |
1435 | T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath"); | |
1436 | char *client_args[] = { path, "-n", "srp_client", NULL }; | |
1437 | pid_t client_pid; | |
1438 | int sp_ret; | |
1439 | kern_return_t kr; | |
f427ee49 A |
1440 | mach_port_t port; |
1441 | ||
1442 | /* setup the signal handler in the parent (server) */ | |
1443 | T_LOG("setup sig handlers"); | |
1444 | signal(SIGUSR1, SIG_IGN); | |
1445 | sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dq); | |
1446 | ||
1447 | dispatch_source_set_event_handler(sig_src, ^{ | |
1448 | dispatch_semaphore_signal(can_continue); | |
ea3f0419 | 1449 | }); |
f427ee49 A |
1450 | dispatch_activate(sig_src); |
1451 | ||
1452 | /* register with the mach service name so the client can lookup and send a message to the parent (server) */ | |
1453 | T_LOG("Server about to check in"); | |
1454 | kr = bootstrap_check_in(bootstrap_port, SRP_SERVICE_NAME, &port); | |
1455 | T_ASSERT_MACH_SUCCESS(kr, "server bootstrap_check_in"); | |
1456 | ||
1457 | T_LOG("Launching client"); | |
1458 | sp_ret = posix_spawn(&client_pid, client_args[0], NULL, NULL, client_args, NULL); | |
1459 | T_QUIET; T_ASSERT_POSIX_ZERO(sp_ret, "spawned process '%s' with PID %d", client_args[0], client_pid); | |
1460 | T_LOG("Spawned client as PID %d", client_pid); | |
1461 | ||
1462 | dispatch_semaphore_wait(can_continue, DISPATCH_TIME_FOREVER); | |
1463 | T_LOG("Ready to take stackshot, but waiting 1s for the coast to clear"); | |
1464 | ||
c3c9b80d A |
1465 | /* |
1466 | * can_continue indicates the client has signaled us, but we want to make | |
1467 | * sure they've actually blocked sending their mach message. It's cheesy, but | |
1468 | * sleep() works for this. | |
1469 | */ | |
f427ee49 A |
1470 | sleep(1); |
1471 | ||
1472 | /* | |
1473 | * take the stackshot without calling receive to verify that the stackshot wait | |
c3c9b80d | 1474 | * info shows our (the server) thread for the scenario where the server has yet to |
f427ee49 A |
1475 | * receive the message. |
1476 | */ | |
1477 | T_LOG("Taking stackshot for part 1 coverage"); | |
c3c9b80d | 1478 | check_srp_test("srp", SRP_TEST_THREAD); |
f427ee49 A |
1479 | |
1480 | /* | |
1481 | * receive the message from the client (which should copy the send once right into | |
1482 | * our address space). | |
1483 | */ | |
1484 | struct { | |
1485 | mach_msg_header_t header; | |
1486 | mach_msg_body_t body; | |
1487 | mach_msg_port_descriptor_t port_descriptor; | |
1488 | } rcv_msg = { | |
1489 | .header = | |
1490 | { | |
1491 | .msgh_remote_port = MACH_PORT_NULL, | |
1492 | .msgh_local_port = port, | |
1493 | .msgh_size = sizeof(rcv_msg), | |
1494 | }, | |
1495 | }; | |
1496 | ||
1497 | T_LOG("server: starting sync receive\n"); | |
1498 | ||
1499 | mach_msg_return_t mr; | |
1500 | mr = mach_msg(&(rcv_msg.header), | |
1501 | (MACH_RCV_MSG | MACH_RCV_TIMEOUT), | |
1502 | 0, | |
1503 | 4096, | |
1504 | port, | |
1505 | 10000, | |
1506 | MACH_PORT_NULL); | |
1507 | T_QUIET; T_ASSERT_MACH_SUCCESS(mr, "mach_msg() recieve of message from client"); | |
1508 | ||
1509 | /* | |
1510 | * take the stackshot to verify that the stackshot wait info shows our (the server) PID | |
1511 | * for the scenario where the server has received the message and copied in the send-once right. | |
1512 | */ | |
1513 | T_LOG("Taking stackshot for part 2 coverage"); | |
c3c9b80d | 1514 | check_srp_test("srp", SRP_TEST_PID); |
f427ee49 A |
1515 | |
1516 | /* cleanup - kill the client */ | |
c3c9b80d A |
1517 | T_ASSERT_POSIX_SUCCESS(kill(client_pid, SIGKILL), "killing client"); |
1518 | T_ASSERT_POSIX_SUCCESS(waitpid(client_pid, NULL, 0), "waiting for the client to exit"); | |
1519 | ||
1520 | // do it again, but using kevents | |
1521 | T_LOG("Launching client"); | |
1522 | sp_ret = posix_spawn(&client_pid, client_args[0], NULL, NULL, client_args, NULL); | |
1523 | T_QUIET; T_ASSERT_POSIX_ZERO(sp_ret, "spawned process '%s' with PID %d", client_args[0], client_pid); | |
1524 | T_LOG("Spawned client as PID %d", client_pid); | |
f427ee49 | 1525 | |
c3c9b80d A |
1526 | dispatch_semaphore_wait(can_continue, DISPATCH_TIME_FOREVER); |
1527 | T_LOG("Ready to take stackshot, but waiting 1s for the coast to clear"); | |
1528 | ||
1529 | /* | |
1530 | * can_continue indicates the client has signaled us, but we want to make | |
1531 | * sure they've actually blocked sending their mach message. It's cheesy, but | |
1532 | * sleep() works for this. | |
1533 | */ | |
1534 | sleep(1); | |
1535 | ||
1536 | dispatch_mach_t dispatch_mach = dispatch_mach_create(SRP_SERVICE_NAME, machdq, | |
1537 | ^(dispatch_mach_reason_t reason, | |
1538 | dispatch_mach_msg_t message, | |
1539 | mach_error_t error __unused) { | |
1540 | switch (reason) { | |
1541 | case DISPATCH_MACH_MESSAGE_RECEIVED: { | |
1542 | size_t size = 0; | |
1543 | mach_msg_header_t *msg __unused = dispatch_mach_msg_get_msg(message, &size); | |
1544 | T_LOG("server: recieved %ld byte message", size); | |
1545 | check_srp_test("turnstile_port_thread", SRP_TEST_THREAD); | |
1546 | T_LOG("server: letting client go"); | |
1547 | // drop the message on the ground, we'll kill the client later | |
1548 | dispatch_semaphore_signal(can_continue); | |
1549 | break; | |
1550 | } | |
1551 | default: | |
1552 | break; | |
1553 | } | |
1554 | }); | |
1555 | ||
1556 | dispatch_mach_connect(dispatch_mach, port, MACH_PORT_NULL, NULL); | |
1557 | ||
1558 | dispatch_semaphore_wait(can_continue, DISPATCH_TIME_FOREVER); | |
1559 | ||
1560 | /* cleanup - kill the client */ | |
1561 | T_ASSERT_POSIX_SUCCESS(kill(client_pid, SIGKILL), "killing client"); | |
1562 | T_ASSERT_POSIX_SUCCESS(waitpid(client_pid, NULL, 0), "waiting for the client to exit"); | |
ea3f0419 A |
1563 | } |
1564 | ||
d9a64523 A |
1565 | #pragma mark performance tests |
1566 | ||
1567 | #define SHOULD_REUSE_SIZE_HINT 0x01 | |
1568 | #define SHOULD_USE_DELTA 0x02 | |
1569 | #define SHOULD_TARGET_SELF 0x04 | |
1570 | ||
1571 | static void | |
1572 | stackshot_perf(unsigned int options) | |
1573 | { | |
1574 | struct scenario scenario = { | |
1575 | .flags = (STACKSHOT_SAVE_LOADINFO | STACKSHOT_GET_GLOBAL_MEM_STATS | |
1576 | | STACKSHOT_SAVE_IMP_DONATION_PIDS | STACKSHOT_KCDATA_FORMAT), | |
1577 | }; | |
1578 | ||
1579 | dt_stat_t size = dt_stat_create("bytes", "size"); | |
1580 | dt_stat_time_t duration = dt_stat_time_create("duration"); | |
1581 | scenario.timer = duration; | |
1582 | ||
1583 | if (options & SHOULD_TARGET_SELF) { | |
1584 | scenario.target_pid = getpid(); | |
1585 | } | |
1586 | ||
1587 | while (!dt_stat_stable(duration) || !dt_stat_stable(size)) { | |
1588 | __block uint64_t last_time = 0; | |
1589 | __block uint32_t size_hint = 0; | |
f427ee49 | 1590 | take_stackshot(&scenario, false, ^(void *ssbuf, size_t sslen) { |
d9a64523 A |
1591 | dt_stat_add(size, (double)sslen); |
1592 | last_time = stackshot_timestamp(ssbuf, sslen); | |
1593 | size_hint = (uint32_t)sslen; | |
1594 | }); | |
1595 | if (options & SHOULD_USE_DELTA) { | |
1596 | scenario.since_timestamp = last_time; | |
1597 | scenario.flags |= STACKSHOT_COLLECT_DELTA_SNAPSHOT; | |
1598 | } | |
1599 | if (options & SHOULD_REUSE_SIZE_HINT) { | |
1600 | scenario.size_hint = size_hint; | |
1601 | } | |
1602 | } | |
1603 | ||
1604 | dt_stat_finalize(duration); | |
1605 | dt_stat_finalize(size); | |
1606 | } | |
1607 | ||
f427ee49 A |
1608 | static void |
1609 | stackshot_flag_perf_noclobber(uint64_t flag, char *flagname) | |
1610 | { | |
1611 | struct scenario scenario = { | |
1612 | .quiet = true, | |
1613 | .flags = (flag | STACKSHOT_KCDATA_FORMAT), | |
1614 | }; | |
1615 | ||
1616 | dt_stat_t duration = dt_stat_create("nanoseconds per thread", "%s_duration", flagname); | |
1617 | dt_stat_t size = dt_stat_create("bytes per thread", "%s_size", flagname); | |
c3c9b80d | 1618 | T_LOG("Testing \"%s\" = 0x%" PRIx64, flagname, flag); |
f427ee49 A |
1619 | |
1620 | while (!dt_stat_stable(duration) || !dt_stat_stable(size)) { | |
1621 | take_stackshot(&scenario, false, ^(void *ssbuf, size_t sslen) { | |
1622 | kcdata_iter_t iter = kcdata_iter(ssbuf, sslen); | |
1623 | unsigned long no_threads = 0; | |
1624 | mach_timebase_info_data_t timebase = {0, 0}; | |
1625 | uint64_t stackshot_duration = 0; | |
1626 | int found = 0; | |
1627 | T_QUIET; T_ASSERT_EQ(kcdata_iter_type(iter), KCDATA_BUFFER_BEGIN_STACKSHOT, "stackshot buffer"); | |
1628 | ||
1629 | KCDATA_ITER_FOREACH(iter) { | |
1630 | switch(kcdata_iter_type(iter)) { | |
1631 | case STACKSHOT_KCTYPE_THREAD_SNAPSHOT: { | |
1632 | found |= 1; | |
1633 | no_threads ++; | |
1634 | break; | |
1635 | } | |
1636 | case STACKSHOT_KCTYPE_STACKSHOT_DURATION: { | |
1637 | struct stackshot_duration *ssd = kcdata_iter_payload(iter); | |
1638 | stackshot_duration = ssd->stackshot_duration; | |
1639 | found |= 2; | |
1640 | break; | |
1641 | } | |
1642 | case KCDATA_TYPE_TIMEBASE: { | |
1643 | found |= 4; | |
1644 | mach_timebase_info_data_t *tb = kcdata_iter_payload(iter); | |
1645 | memcpy(&timebase, tb, sizeof(timebase)); | |
1646 | break; | |
1647 | } | |
1648 | } | |
1649 | } | |
1650 | ||
1651 | T_QUIET; T_ASSERT_EQ(found, 0x7, "found everything needed"); | |
1652 | ||
1653 | uint64_t ns = (stackshot_duration * timebase.numer) / timebase.denom; | |
1654 | uint64_t per_thread_ns = ns / no_threads; | |
1655 | uint64_t per_thread_size = sslen / no_threads; | |
1656 | ||
1657 | dt_stat_add(duration, per_thread_ns); | |
1658 | dt_stat_add(size, per_thread_size); | |
1659 | }); | |
1660 | } | |
1661 | ||
1662 | dt_stat_finalize(duration); | |
1663 | dt_stat_finalize(size); | |
1664 | } | |
1665 | ||
1666 | static void | |
1667 | stackshot_flag_perf(uint64_t flag, char *flagname) | |
1668 | { | |
1669 | /* | |
1670 | * STACKSHOT_NO_IO_STATS disables data collection, so set it for | |
1671 | * more accurate perfdata collection. | |
1672 | */ | |
1673 | flag |= STACKSHOT_NO_IO_STATS; | |
1674 | ||
1675 | stackshot_flag_perf_noclobber(flag, flagname); | |
1676 | } | |
1677 | ||
1678 | ||
1679 | T_DECL(flag_perf, "test stackshot performance with different flags set", T_META_TAG_PERF) | |
1680 | { | |
1681 | stackshot_flag_perf_noclobber(STACKSHOT_NO_IO_STATS, "baseline"); | |
1682 | stackshot_flag_perf_noclobber(0, "io_stats"); | |
1683 | ||
1684 | stackshot_flag_perf(STACKSHOT_THREAD_WAITINFO, "thread_waitinfo"); | |
1685 | stackshot_flag_perf(STACKSHOT_GET_DQ, "get_dq"); | |
1686 | stackshot_flag_perf(STACKSHOT_SAVE_LOADINFO, "save_loadinfo"); | |
1687 | stackshot_flag_perf(STACKSHOT_GET_GLOBAL_MEM_STATS, "get_global_mem_stats"); | |
1688 | stackshot_flag_perf(STACKSHOT_SAVE_KEXT_LOADINFO, "save_kext_loadinfo"); | |
1689 | stackshot_flag_perf(STACKSHOT_SAVE_IMP_DONATION_PIDS, "save_imp_donation_pids"); | |
1690 | stackshot_flag_perf(STACKSHOT_ENABLE_BT_FAULTING, "enable_bt_faulting"); | |
1691 | stackshot_flag_perf(STACKSHOT_COLLECT_SHAREDCACHE_LAYOUT, "collect_sharedcache_layout"); | |
1692 | stackshot_flag_perf(STACKSHOT_ENABLE_UUID_FAULTING, "enable_uuid_faulting"); | |
1693 | stackshot_flag_perf(STACKSHOT_THREAD_GROUP, "thread_group"); | |
1694 | stackshot_flag_perf(STACKSHOT_SAVE_JETSAM_COALITIONS, "save_jetsam_coalitions"); | |
1695 | stackshot_flag_perf(STACKSHOT_INSTRS_CYCLES, "instrs_cycles"); | |
1696 | stackshot_flag_perf(STACKSHOT_ASID, "asid"); | |
1697 | } | |
1698 | ||
d9a64523 A |
1699 | T_DECL(perf_no_size_hint, "test stackshot performance with no size hint", |
1700 | T_META_TAG_PERF) | |
1701 | { | |
1702 | stackshot_perf(0); | |
1703 | } | |
1704 | ||
1705 | T_DECL(perf_size_hint, "test stackshot performance with size hint", | |
1706 | T_META_TAG_PERF) | |
1707 | { | |
1708 | stackshot_perf(SHOULD_REUSE_SIZE_HINT); | |
1709 | } | |
1710 | ||
1711 | T_DECL(perf_process, "test stackshot performance targeted at process", | |
1712 | T_META_TAG_PERF) | |
1713 | { | |
1714 | stackshot_perf(SHOULD_REUSE_SIZE_HINT | SHOULD_TARGET_SELF); | |
1715 | } | |
1716 | ||
1717 | T_DECL(perf_delta, "test delta stackshot performance", | |
1718 | T_META_TAG_PERF) | |
1719 | { | |
1720 | stackshot_perf(SHOULD_REUSE_SIZE_HINT | SHOULD_USE_DELTA); | |
1721 | } | |
1722 | ||
1723 | T_DECL(perf_delta_process, "test delta stackshot performance targeted at a process", | |
1724 | T_META_TAG_PERF) | |
1725 | { | |
1726 | stackshot_perf(SHOULD_REUSE_SIZE_HINT | SHOULD_USE_DELTA | SHOULD_TARGET_SELF); | |
1727 | } | |
1728 | ||
1729 | static uint64_t | |
1730 | stackshot_timestamp(void *ssbuf, size_t sslen) | |
1731 | { | |
1732 | kcdata_iter_t iter = kcdata_iter(ssbuf, sslen); | |
1733 | ||
1734 | uint32_t type = kcdata_iter_type(iter); | |
1735 | if (type != KCDATA_BUFFER_BEGIN_STACKSHOT && type != KCDATA_BUFFER_BEGIN_DELTA_STACKSHOT) { | |
1736 | T_ASSERT_FAIL("invalid kcdata type %u", kcdata_iter_type(iter)); | |
1737 | } | |
1738 | ||
1739 | iter = kcdata_iter_find_type(iter, KCDATA_TYPE_MACH_ABSOLUTE_TIME); | |
1740 | T_QUIET; | |
1741 | T_ASSERT_TRUE(kcdata_iter_valid(iter), "timestamp found in stackshot"); | |
1742 | ||
1743 | return *(uint64_t *)kcdata_iter_payload(iter); | |
1744 | } | |
1745 | ||
1746 | #define TEST_THREAD_NAME "stackshot_test_thread" | |
1747 | ||
1748 | static void | |
1749 | parse_thread_group_stackshot(void **ssbuf, size_t sslen) | |
1750 | { | |
1751 | bool seen_thread_group_snapshot = false; | |
1752 | kcdata_iter_t iter = kcdata_iter(ssbuf, sslen); | |
1753 | T_ASSERT_EQ(kcdata_iter_type(iter), KCDATA_BUFFER_BEGIN_STACKSHOT, | |
1754 | "buffer provided is a stackshot"); | |
1755 | ||
1756 | NSMutableSet *thread_groups = [[NSMutableSet alloc] init]; | |
1757 | ||
1758 | iter = kcdata_iter_next(iter); | |
1759 | KCDATA_ITER_FOREACH(iter) { | |
1760 | switch (kcdata_iter_type(iter)) { | |
1761 | case KCDATA_TYPE_ARRAY: { | |
1762 | T_QUIET; | |
1763 | T_ASSERT_TRUE(kcdata_iter_array_valid(iter), | |
1764 | "checked that array is valid"); | |
1765 | ||
1766 | if (kcdata_iter_array_elem_type(iter) != STACKSHOT_KCTYPE_THREAD_GROUP_SNAPSHOT) { | |
1767 | continue; | |
1768 | } | |
1769 | ||
1770 | seen_thread_group_snapshot = true; | |
1771 | ||
1772 | if (kcdata_iter_array_elem_size(iter) >= sizeof(struct thread_group_snapshot_v2)) { | |
1773 | struct thread_group_snapshot_v2 *tgs_array = kcdata_iter_payload(iter); | |
1774 | for (uint32_t j = 0; j < kcdata_iter_array_elem_count(iter); j++) { | |
1775 | struct thread_group_snapshot_v2 *tgs = tgs_array + j; | |
1776 | [thread_groups addObject:@(tgs->tgs_id)]; | |
1777 | } | |
1778 | ||
1779 | } | |
1780 | else { | |
1781 | struct thread_group_snapshot *tgs_array = kcdata_iter_payload(iter); | |
1782 | for (uint32_t j = 0; j < kcdata_iter_array_elem_count(iter); j++) { | |
1783 | struct thread_group_snapshot *tgs = tgs_array + j; | |
1784 | [thread_groups addObject:@(tgs->tgs_id)]; | |
1785 | } | |
1786 | } | |
1787 | break; | |
1788 | } | |
1789 | } | |
1790 | } | |
1791 | KCDATA_ITER_FOREACH(iter) { | |
1792 | NSError *error = nil; | |
1793 | ||
1794 | switch (kcdata_iter_type(iter)) { | |
1795 | ||
1796 | case KCDATA_TYPE_CONTAINER_BEGIN: { | |
1797 | T_QUIET; | |
1798 | T_ASSERT_TRUE(kcdata_iter_container_valid(iter), | |
1799 | "checked that container is valid"); | |
1800 | ||
1801 | if (kcdata_iter_container_type(iter) != STACKSHOT_KCCONTAINER_THREAD) { | |
1802 | break; | |
1803 | } | |
1804 | ||
1805 | NSDictionary *container = parseKCDataContainer(&iter, &error); | |
1806 | T_QUIET; T_ASSERT_NOTNULL(container, "parsed container from stackshot"); | |
1807 | T_QUIET; T_ASSERT_NULL(error, "error unset after parsing container"); | |
1808 | ||
1809 | int tg = [container[@"thread_snapshots"][@"thread_group"] intValue]; | |
1810 | ||
1811 | T_ASSERT_TRUE([thread_groups containsObject:@(tg)], "check that the thread group the thread is in exists"); | |
1812 | ||
1813 | break; | |
1814 | }; | |
1815 | ||
1816 | } | |
1817 | } | |
1818 | T_ASSERT_TRUE(seen_thread_group_snapshot, "check that we have seen a thread group snapshot"); | |
1819 | } | |
1820 | ||
1821 | static void | |
1822 | verify_stackshot_sharedcache_layout(struct dyld_uuid_info_64 *uuids, uint32_t uuid_count) | |
1823 | { | |
1824 | uuid_t cur_shared_cache_uuid; | |
1825 | __block uint32_t lib_index = 0, libs_found = 0; | |
1826 | ||
1827 | _dyld_get_shared_cache_uuid(cur_shared_cache_uuid); | |
1828 | int result = dyld_shared_cache_iterate_text(cur_shared_cache_uuid, ^(const dyld_shared_cache_dylib_text_info* info) { | |
1829 | T_QUIET; T_ASSERT_LT(lib_index, uuid_count, "dyld_shared_cache_iterate_text exceeded number of libraries returned by kernel"); | |
1830 | ||
1831 | libs_found++; | |
1832 | struct dyld_uuid_info_64 *cur_stackshot_uuid_entry = &uuids[lib_index]; | |
1833 | T_QUIET; T_ASSERT_EQ(memcmp(info->dylibUuid, cur_stackshot_uuid_entry->imageUUID, sizeof(info->dylibUuid)), 0, | |
1834 | "dyld returned UUID doesn't match kernel returned UUID"); | |
1835 | T_QUIET; T_ASSERT_EQ(info->loadAddressUnslid, cur_stackshot_uuid_entry->imageLoadAddress, | |
1836 | "dyld returned load address doesn't match kernel returned load address"); | |
1837 | lib_index++; | |
1838 | }); | |
1839 | ||
1840 | T_ASSERT_EQ(result, 0, "iterate shared cache layout"); | |
1841 | T_ASSERT_EQ(libs_found, uuid_count, "dyld iterator returned same number of libraries as kernel"); | |
1842 | ||
1843 | T_LOG("verified %d libraries from dyld shared cache", libs_found); | |
1844 | } | |
1845 | ||
1846 | static void | |
f427ee49 A |
1847 | check_shared_cache_uuid(uuid_t imageUUID) |
1848 | { | |
1849 | static uuid_t shared_cache_uuid; | |
1850 | static dispatch_once_t read_shared_cache_uuid; | |
1851 | ||
1852 | dispatch_once(&read_shared_cache_uuid, ^{ | |
1853 | T_QUIET; | |
1854 | T_ASSERT_TRUE(_dyld_get_shared_cache_uuid(shared_cache_uuid), "retrieve current shared cache UUID"); | |
1855 | }); | |
1856 | T_QUIET; T_ASSERT_EQ(uuid_compare(shared_cache_uuid, imageUUID), 0, | |
1857 | "dyld returned UUID doesn't match kernel returned UUID for system shared cache"); | |
1858 | } | |
1859 | ||
1860 | /* | |
1861 | * extra dictionary contains data relevant for the given flags: | |
1862 | * PARSE_STACKSHOT_ZOMBIE: zombie_child_pid_key -> @(pid) | |
1863 | * PARSE_STACKSHOT_POSTEXEC: postexec_child_unique_pid_key -> @(unique_pid) | |
1864 | */ | |
1865 | static void | |
1866 | parse_stackshot(uint64_t stackshot_parsing_flags, void *ssbuf, size_t sslen, NSDictionary *extra) | |
d9a64523 A |
1867 | { |
1868 | bool delta = (stackshot_parsing_flags & PARSE_STACKSHOT_DELTA); | |
c3c9b80d | 1869 | bool expect_sharedcache_child = (stackshot_parsing_flags & PARSE_STACKSHOT_SHAREDCACHE_FLAGS); |
d9a64523 | 1870 | bool expect_zombie_child = (stackshot_parsing_flags & PARSE_STACKSHOT_ZOMBIE); |
f427ee49 | 1871 | bool expect_postexec_child = (stackshot_parsing_flags & PARSE_STACKSHOT_POSTEXEC); |
ea3f0419 | 1872 | bool expect_cseg_waitinfo = (stackshot_parsing_flags & PARSE_STACKSHOT_WAITINFO_CSEG); |
f427ee49 | 1873 | bool expect_translated_child = (stackshot_parsing_flags & PARSE_STACKSHOT_TRANSLATED); |
d9a64523 A |
1874 | bool expect_shared_cache_layout = false; |
1875 | bool expect_shared_cache_uuid = !delta; | |
cb323159 A |
1876 | bool expect_dispatch_queue_label = (stackshot_parsing_flags & PARSE_STACKSHOT_DISPATCH_QUEUE_LABEL); |
1877 | bool expect_turnstile_lock = (stackshot_parsing_flags & PARSE_STACKSHOT_TURNSTILEINFO); | |
f427ee49 A |
1878 | bool expect_srp_waitinfo = (stackshot_parsing_flags & PARSE_STACKSHOT_WAITINFO_SRP); |
1879 | bool found_zombie_child = false, found_postexec_child = false, found_shared_cache_layout = false, found_shared_cache_uuid = false; | |
1880 | bool found_translated_child = false; | |
cb323159 | 1881 | bool found_dispatch_queue_label = false, found_turnstile_lock = false; |
f427ee49 | 1882 | bool found_cseg_waitinfo = false, found_srp_waitinfo = false; |
c3c9b80d A |
1883 | bool found_sharedcache_child = false, found_sharedcache_badflags = false, found_sharedcache_self = false; |
1884 | uint64_t srp_expected_threadid = 0; | |
1885 | pid_t zombie_child_pid = -1, srp_expected_pid = -1, sharedcache_child_pid = -1; | |
f427ee49 | 1886 | pid_t translated_child_pid = -1; |
c3c9b80d | 1887 | bool sharedcache_child_sameaddr = false; |
f427ee49 | 1888 | uint64_t postexec_child_unique_pid = 0, cseg_expected_threadid = 0; |
c3c9b80d | 1889 | uint64_t sharedcache_child_flags = 0, sharedcache_self_flags = 0; |
f427ee49 | 1890 | char *inflatedBufferBase = NULL; |
cb323159 A |
1891 | |
1892 | if (expect_shared_cache_uuid) { | |
1893 | uuid_t shared_cache_uuid; | |
1894 | if (!_dyld_get_shared_cache_uuid(shared_cache_uuid)) { | |
1895 | T_LOG("Skipping verifying shared cache UUID in stackshot data because not running with a shared cache"); | |
1896 | expect_shared_cache_uuid = false; | |
1897 | } | |
1898 | } | |
d9a64523 A |
1899 | |
1900 | if (stackshot_parsing_flags & PARSE_STACKSHOT_SHAREDCACHE_LAYOUT) { | |
1901 | size_t shared_cache_length = 0; | |
cb323159 | 1902 | const void *cache_header = _dyld_get_shared_cache_range(&shared_cache_length); |
d9a64523 A |
1903 | T_QUIET; T_ASSERT_NOTNULL(cache_header, "current process running with shared cache"); |
1904 | T_QUIET; T_ASSERT_GT(shared_cache_length, sizeof(struct _dyld_cache_header), "valid shared cache length populated by _dyld_get_shared_cache_range"); | |
1905 | ||
cb323159 | 1906 | if (_dyld_shared_cache_is_locally_built()) { |
d9a64523 A |
1907 | T_LOG("device running with locally built shared cache, expect shared cache layout"); |
1908 | expect_shared_cache_layout = true; | |
1909 | } else { | |
1910 | T_LOG("device running with B&I built shared-cache, no shared cache layout expected"); | |
1911 | } | |
1912 | } | |
1913 | ||
c3c9b80d A |
1914 | if (expect_sharedcache_child) { |
1915 | NSNumber* pid_num = extra[sharedcache_child_pid_key]; | |
1916 | NSNumber* sameaddr_num = extra[sharedcache_child_sameaddr_key]; | |
1917 | T_QUIET; T_ASSERT_NOTNULL(pid_num, "sharedcache child pid provided"); | |
1918 | T_QUIET; T_ASSERT_NOTNULL(sameaddr_num, "sharedcache child addrsame provided"); | |
1919 | sharedcache_child_pid = [pid_num intValue]; | |
1920 | T_QUIET; T_ASSERT_GT(sharedcache_child_pid, 0, "sharedcache child pid greater than zero"); | |
1921 | sharedcache_child_sameaddr = [sameaddr_num intValue]; | |
1922 | T_QUIET; T_ASSERT_GE([sameaddr_num intValue], 0, "sharedcache child sameaddr is boolean (0 or 1)"); | |
1923 | T_QUIET; T_ASSERT_LE([sameaddr_num intValue], 1, "sharedcache child sameaddr is boolean (0 or 1)"); | |
1924 | } | |
d9a64523 | 1925 | if (expect_zombie_child) { |
f427ee49 A |
1926 | NSNumber* pid_num = extra[zombie_child_pid_key]; |
1927 | T_QUIET; T_ASSERT_NOTNULL(pid_num, "zombie child pid provided"); | |
1928 | zombie_child_pid = [pid_num intValue]; | |
1929 | T_QUIET; T_ASSERT_GT(zombie_child_pid, 0, "zombie child pid greater than zero"); | |
1930 | } | |
1931 | ||
1932 | if (expect_postexec_child) { | |
1933 | NSNumber* unique_pid_num = extra[postexec_child_unique_pid_key]; | |
1934 | T_QUIET; T_ASSERT_NOTNULL(unique_pid_num, "postexec child unique pid provided"); | |
1935 | postexec_child_unique_pid = [unique_pid_num unsignedLongLongValue]; | |
1936 | T_QUIET; T_ASSERT_GT(postexec_child_unique_pid, 0ull, "postexec child unique pid greater than zero"); | |
d9a64523 A |
1937 | } |
1938 | ||
f427ee49 A |
1939 | if (expect_cseg_waitinfo) { |
1940 | NSNumber* tid_num = extra[cseg_expected_threadid_key]; | |
1941 | T_QUIET; T_ASSERT_NOTNULL(tid_num, "cseg's expected thread id provided"); | |
c3c9b80d A |
1942 | cseg_expected_threadid = tid_num.unsignedLongValue; |
1943 | T_QUIET; T_ASSERT_GT(cseg_expected_threadid, UINT64_C(0), "compressor segment thread is present"); | |
f427ee49 A |
1944 | } |
1945 | ||
1946 | if (expect_srp_waitinfo) { | |
c3c9b80d | 1947 | NSNumber* threadid_num = extra[srp_expected_threadid_key]; |
f427ee49 | 1948 | NSNumber* pid_num = extra[srp_expected_pid_key]; |
c3c9b80d A |
1949 | T_QUIET; T_ASSERT_TRUE(threadid_num != nil || pid_num != nil, "expected SRP threadid or pid"); |
1950 | if (threadid_num != nil) { | |
1951 | srp_expected_threadid = [threadid_num unsignedLongLongValue]; | |
1952 | T_QUIET; T_ASSERT_GT(srp_expected_threadid, 0ull, "srp_expected_threadid greater than zero"); | |
1953 | } | |
1954 | if (pid_num != nil) { | |
1955 | srp_expected_pid = [pid_num intValue]; | |
1956 | T_QUIET; T_ASSERT_GT(srp_expected_pid, 0, "srp_expected_pid greater than zero"); | |
1957 | } | |
1958 | T_LOG("looking for SRP pid: %d threadid: %llu", srp_expected_pid, srp_expected_threadid); | |
f427ee49 A |
1959 | } |
1960 | ||
1961 | if (expect_translated_child) { | |
1962 | NSNumber* pid_num = extra[translated_child_pid_key]; | |
1963 | T_QUIET; T_ASSERT_NOTNULL(pid_num, "translated child pid provided"); | |
1964 | translated_child_pid = [pid_num intValue]; | |
1965 | T_QUIET; T_ASSERT_GT(translated_child_pid, 0, "translated child pid greater than zero"); | |
1966 | } | |
c3c9b80d | 1967 | |
d9a64523 A |
1968 | kcdata_iter_t iter = kcdata_iter(ssbuf, sslen); |
1969 | if (delta) { | |
1970 | T_ASSERT_EQ(kcdata_iter_type(iter), KCDATA_BUFFER_BEGIN_DELTA_STACKSHOT, | |
1971 | "buffer provided is a delta stackshot"); | |
f427ee49 A |
1972 | |
1973 | iter = kcdata_iter_next(iter); | |
d9a64523 | 1974 | } else { |
f427ee49 A |
1975 | if (kcdata_iter_type(iter) != KCDATA_BUFFER_BEGIN_COMPRESSED) { |
1976 | T_ASSERT_EQ(kcdata_iter_type(iter), KCDATA_BUFFER_BEGIN_STACKSHOT, | |
1977 | "buffer provided is a stackshot"); | |
1978 | ||
1979 | iter = kcdata_iter_next(iter); | |
1980 | } else { | |
1981 | /* we are dealing with a compressed buffer */ | |
1982 | iter = kcdata_iter_next(iter); | |
1983 | uint64_t compression_type = 0, totalout = 0, totalin = 0; | |
1984 | ||
1985 | uint64_t *data; | |
1986 | char *desc; | |
1987 | for (int i = 0; i < 3; i ++) { | |
c3c9b80d | 1988 | kcdata_iter_get_data_with_desc(iter, &desc, (void **)&data, NULL); |
f427ee49 A |
1989 | if (strcmp(desc, "kcd_c_type") == 0) { |
1990 | compression_type = *data; | |
1991 | } else if (strcmp(desc, "kcd_c_totalout") == 0){ | |
1992 | totalout = *data; | |
1993 | } else if (strcmp(desc, "kcd_c_totalin") == 0){ | |
1994 | totalin = *data; | |
1995 | } | |
1996 | ||
1997 | iter = kcdata_iter_next(iter); | |
1998 | } | |
1999 | ||
c3c9b80d A |
2000 | T_ASSERT_EQ(compression_type, UINT64_C(1), "zlib compression is used"); |
2001 | T_ASSERT_GT(totalout, UINT64_C(0), "successfully gathered how long the compressed buffer is"); | |
2002 | T_ASSERT_GT(totalin, UINT64_C(0), "successfully gathered how long the uncompressed buffer will be at least"); | |
f427ee49 A |
2003 | |
2004 | /* progress to the next kcdata item */ | |
2005 | T_ASSERT_EQ(kcdata_iter_type(iter), KCDATA_BUFFER_BEGIN_STACKSHOT, "compressed stackshot found"); | |
2006 | ||
c3c9b80d | 2007 | char *bufferBase = kcdata_iter_payload(iter); |
f427ee49 A |
2008 | |
2009 | /* | |
2010 | * zlib is used, allocate a buffer based on the metadata, plus | |
2011 | * extra scratch space (+12.5%) in case totalin was inconsistent | |
2012 | */ | |
2013 | size_t inflatedBufferSize = totalin + (totalin >> 3); | |
2014 | inflatedBufferBase = malloc(inflatedBufferSize); | |
2015 | T_QUIET; T_WITH_ERRNO; T_ASSERT_NOTNULL(inflatedBufferBase, "allocated temporary output buffer"); | |
2016 | ||
2017 | z_stream zs; | |
2018 | memset(&zs, 0, sizeof(zs)); | |
2019 | T_QUIET; T_ASSERT_EQ(inflateInit(&zs), Z_OK, "inflateInit OK"); | |
c3c9b80d A |
2020 | zs.next_in = (unsigned char *)bufferBase; |
2021 | T_QUIET; T_ASSERT_LE(totalout, (uint64_t)UINT_MAX, "stackshot is not too large"); | |
2022 | zs.avail_in = (uInt)totalout; | |
2023 | zs.next_out = (unsigned char *)inflatedBufferBase; | |
2024 | T_QUIET; T_ASSERT_LE(inflatedBufferSize, (size_t)UINT_MAX, "output region is not too large"); | |
2025 | zs.avail_out = (uInt)inflatedBufferSize; | |
f427ee49 A |
2026 | T_ASSERT_EQ(inflate(&zs, Z_FINISH), Z_STREAM_END, "inflated buffer"); |
2027 | inflateEnd(&zs); | |
2028 | ||
c3c9b80d | 2029 | T_ASSERT_EQ((uint64_t)zs.total_out, totalin, "expected number of bytes inflated"); |
f427ee49 A |
2030 | |
2031 | /* copy the data after the compressed area */ | |
c3c9b80d A |
2032 | T_QUIET; T_ASSERT_GE((void *)bufferBase, ssbuf, |
2033 | "base of compressed stackshot is after the returned stackshot buffer"); | |
2034 | size_t header_size = (size_t)(bufferBase - (char *)ssbuf); | |
2035 | size_t data_after_compressed_size = sslen - totalout - header_size; | |
2036 | T_QUIET; T_ASSERT_LE(data_after_compressed_size, | |
f427ee49 A |
2037 | inflatedBufferSize - zs.total_out, |
2038 | "footer fits in the buffer"); | |
2039 | memcpy(inflatedBufferBase + zs.total_out, | |
2040 | bufferBase + totalout, | |
c3c9b80d | 2041 | data_after_compressed_size); |
f427ee49 A |
2042 | |
2043 | iter = kcdata_iter(inflatedBufferBase, inflatedBufferSize); | |
2044 | } | |
d9a64523 A |
2045 | } |
2046 | ||
d9a64523 A |
2047 | KCDATA_ITER_FOREACH(iter) { |
2048 | NSError *error = nil; | |
2049 | ||
2050 | switch (kcdata_iter_type(iter)) { | |
2051 | case KCDATA_TYPE_ARRAY: { | |
2052 | T_QUIET; | |
2053 | T_ASSERT_TRUE(kcdata_iter_array_valid(iter), | |
2054 | "checked that array is valid"); | |
2055 | ||
2056 | NSMutableDictionary *array = parseKCDataArray(iter, &error); | |
2057 | T_QUIET; T_ASSERT_NOTNULL(array, "parsed array from stackshot"); | |
2058 | T_QUIET; T_ASSERT_NULL(error, "error unset after parsing array"); | |
2059 | ||
2060 | if (kcdata_iter_array_elem_type(iter) == STACKSHOT_KCTYPE_SYS_SHAREDCACHE_LAYOUT) { | |
2061 | struct dyld_uuid_info_64 *shared_cache_uuids = kcdata_iter_payload(iter); | |
2062 | uint32_t uuid_count = kcdata_iter_array_elem_count(iter); | |
2063 | T_ASSERT_NOTNULL(shared_cache_uuids, "parsed shared cache layout array"); | |
2064 | T_ASSERT_GT(uuid_count, 0, "returned valid number of UUIDs from shared cache"); | |
2065 | verify_stackshot_sharedcache_layout(shared_cache_uuids, uuid_count); | |
2066 | found_shared_cache_layout = true; | |
2067 | } | |
2068 | ||
2069 | break; | |
2070 | } | |
2071 | ||
2072 | case KCDATA_TYPE_CONTAINER_BEGIN: { | |
2073 | T_QUIET; | |
2074 | T_ASSERT_TRUE(kcdata_iter_container_valid(iter), | |
2075 | "checked that container is valid"); | |
2076 | ||
2077 | if (kcdata_iter_container_type(iter) != STACKSHOT_KCCONTAINER_TASK) { | |
2078 | break; | |
2079 | } | |
2080 | ||
2081 | NSDictionary *container = parseKCDataContainer(&iter, &error); | |
2082 | T_QUIET; T_ASSERT_NOTNULL(container, "parsed container from stackshot"); | |
2083 | T_QUIET; T_ASSERT_NULL(error, "error unset after parsing container"); | |
2084 | ||
f427ee49 A |
2085 | NSDictionary* task_snapshot = container[@"task_snapshots"][@"task_snapshot"]; |
2086 | NSDictionary* task_delta_snapshot = container[@"task_snapshots"][@"task_delta_snapshot"]; | |
2087 | ||
2088 | T_QUIET; T_ASSERT_TRUE(!!task_snapshot != !!task_delta_snapshot, "Either task_snapshot xor task_delta_snapshot provided"); | |
2089 | ||
cb323159 A |
2090 | if (expect_dispatch_queue_label && !found_dispatch_queue_label) { |
2091 | for (id thread_key in container[@"task_snapshots"][@"thread_snapshots"]) { | |
2092 | NSMutableDictionary *thread = container[@"task_snapshots"][@"thread_snapshots"][thread_key]; | |
2093 | NSString *dql = thread[@"dispatch_queue_label"]; | |
2094 | ||
2095 | if ([dql isEqualToString:@TEST_STACKSHOT_QUEUE_LABEL]) { | |
2096 | found_dispatch_queue_label = true; | |
2097 | break; | |
2098 | } | |
2099 | } | |
2100 | } | |
f427ee49 A |
2101 | |
2102 | if (expect_postexec_child && !found_postexec_child) { | |
2103 | if (task_snapshot) { | |
2104 | uint64_t unique_pid = [task_snapshot[@"ts_unique_pid"] unsignedLongLongValue]; | |
2105 | if (unique_pid == postexec_child_unique_pid) { | |
2106 | found_postexec_child = true; | |
2107 | ||
2108 | T_PASS("post-exec child %llu has a task snapshot", postexec_child_unique_pid); | |
2109 | ||
2110 | break; | |
2111 | } | |
2112 | } | |
2113 | ||
2114 | if (task_delta_snapshot) { | |
2115 | uint64_t unique_pid = [task_delta_snapshot[@"tds_unique_pid"] unsignedLongLongValue]; | |
2116 | if (unique_pid == postexec_child_unique_pid) { | |
2117 | found_postexec_child = true; | |
2118 | ||
2119 | T_FAIL("post-exec child %llu shouldn't have a delta task snapshot", postexec_child_unique_pid); | |
2120 | ||
2121 | break; | |
2122 | } | |
2123 | } | |
2124 | } | |
2125 | ||
2126 | if (!task_snapshot) { | |
2127 | break; | |
2128 | } | |
2129 | ||
2130 | int pid = [task_snapshot[@"ts_pid"] intValue]; | |
2131 | ||
2132 | if (pid && expect_shared_cache_uuid && !found_shared_cache_uuid) { | |
2133 | id ptr = container[@"task_snapshots"][@"shared_cache_dyld_load_info"]; | |
2134 | if (ptr) { | |
2135 | id uuid = ptr[@"imageUUID"]; | |
2136 | ||
2137 | uint8_t uuid_p[16]; | |
c3c9b80d A |
2138 | for (unsigned int i = 0; i < 16; i ++) { |
2139 | NSNumber *uuidByte = uuid[i]; | |
2140 | uuid_p[i] = (uint8_t)uuidByte.charValue; | |
2141 | } | |
f427ee49 A |
2142 | |
2143 | check_shared_cache_uuid(uuid_p); | |
2144 | ||
c3c9b80d A |
2145 | uint64_t baseAddress = (uint64_t)((NSNumber *)ptr[@"imageSlidBaseAddress"]).longLongValue; |
2146 | uint64_t firstMapping = (uint64_t)((NSNumber *)ptr[@"sharedCacheSlidFirstMapping"]).longLongValue; | |
2147 | ||
2148 | T_ASSERT_LE(baseAddress, firstMapping, | |
2149 | "in per-task shared_cache_dyld_load_info, " | |
2150 | "baseAddress <= firstMapping"); | |
2151 | T_ASSERT_GE(baseAddress + (1ull << 29), firstMapping, | |
2152 | "in per-task shared_cache_dyld_load_info, " | |
2153 | "baseAddress + 512meg >= firstMapping"); | |
2154 | ||
2155 | size_t shared_cache_len; | |
2156 | const void *addr = _dyld_get_shared_cache_range(&shared_cache_len); | |
2157 | T_ASSERT_EQ((uint64_t)addr, firstMapping, | |
2158 | "SlidFirstMapping should match shared_cache_range"); | |
2159 | ||
f427ee49 A |
2160 | /* |
2161 | * check_shared_cache_uuid() will assert on failure, so if | |
2162 | * we get here, then we have found the shared cache UUID | |
2163 | * and it's correct | |
2164 | */ | |
c3c9b80d A |
2165 | found_shared_cache_uuid = true; |
2166 | } | |
2167 | } | |
2168 | if (expect_sharedcache_child) { | |
2169 | uint64_t task_flags = [task_snapshot[@"ts_ss_flags"] unsignedLongLongValue]; | |
2170 | uint64_t sharedregion_flags = (task_flags & (kTaskSharedRegionNone | kTaskSharedRegionSystem | kTaskSharedRegionOther)); | |
2171 | id sharedregion_info = container[@"task_snapshots"][@"shared_cache_dyld_load_info"]; | |
2172 | if (!found_sharedcache_badflags) { | |
2173 | T_QUIET; T_ASSERT_NE(sharedregion_flags, 0ll, "one of the kTaskSharedRegion flags should be set on all tasks"); | |
2174 | bool multiple = (sharedregion_flags & (sharedregion_flags - 1)) != 0; | |
2175 | T_QUIET; T_ASSERT_FALSE(multiple, "only one kTaskSharedRegion flag should be set on each task"); | |
2176 | found_sharedcache_badflags = (sharedregion_flags == 0 || multiple); | |
2177 | } | |
2178 | if (pid == 0) { | |
2179 | T_ASSERT_EQ(sharedregion_flags, (uint64_t)kTaskSharedRegionNone, "Kernel proc (pid 0) should have no shared region"); | |
2180 | } else if (pid == sharedcache_child_pid) { | |
2181 | found_sharedcache_child = true; | |
2182 | sharedcache_child_flags = sharedregion_flags; | |
2183 | } else if (pid == getpid()) { | |
2184 | found_sharedcache_self = true; | |
2185 | sharedcache_self_flags = sharedregion_flags; | |
2186 | } | |
2187 | if (sharedregion_flags == kTaskSharedRegionOther && !(task_flags & kTaskSharedRegionInfoUnavailable)) { | |
2188 | T_QUIET; T_ASSERT_NOTNULL(sharedregion_info, "kTaskSharedRegionOther should have a shared_cache_dyld_load_info struct"); | |
2189 | } else { | |
2190 | T_QUIET; T_ASSERT_NULL(sharedregion_info, "expect no shared_cache_dyld_load_info struct"); | |
f427ee49 A |
2191 | } |
2192 | } | |
f427ee49 A |
2193 | if (expect_zombie_child && (pid == zombie_child_pid)) { |
2194 | found_zombie_child = true; | |
2195 | ||
2196 | uint64_t task_flags = [task_snapshot[@"ts_ss_flags"] unsignedLongLongValue]; | |
2197 | T_ASSERT_TRUE((task_flags & kTerminatedSnapshot) == kTerminatedSnapshot, "child zombie marked as terminated"); | |
2198 | ||
2199 | continue; | |
2200 | } | |
2201 | ||
2202 | if (expect_translated_child && (pid == translated_child_pid)) { | |
2203 | found_translated_child = true; | |
2204 | ||
2205 | uint64_t task_flags = [task_snapshot[@"ts_ss_flags"] unsignedLongLongValue]; | |
c3c9b80d | 2206 | T_EXPECT_BITS_SET(task_flags, kTaskIsTranslated, "child marked as translated"); |
f427ee49 A |
2207 | |
2208 | continue; | |
2209 | } | |
cb323159 | 2210 | |
ea3f0419 A |
2211 | if (expect_cseg_waitinfo) { |
2212 | NSArray *winfos = container[@"task_snapshots"][@"thread_waitinfo"]; | |
2213 | ||
2214 | for (id i in winfos) { | |
c3c9b80d A |
2215 | NSNumber *waitType = i[@"wait_type"]; |
2216 | NSNumber *owner = i[@"owner"]; | |
2217 | if (waitType.intValue == kThreadWaitCompressor && | |
2218 | owner.unsignedLongValue == cseg_expected_threadid) { | |
ea3f0419 A |
2219 | found_cseg_waitinfo = true; |
2220 | break; | |
2221 | } | |
2222 | } | |
2223 | } | |
2224 | ||
f427ee49 A |
2225 | if (expect_srp_waitinfo) { |
2226 | NSArray *tinfos = container[@"task_snapshots"][@"thread_turnstileinfo"]; | |
2227 | NSArray *winfos = container[@"task_snapshots"][@"thread_waitinfo"]; | |
f427ee49 A |
2228 | for (id i in tinfos) { |
2229 | if (!found_srp_waitinfo) { | |
c3c9b80d A |
2230 | bool found_thread = false; |
2231 | bool found_pid = false; | |
2232 | if (([i[@"turnstile_flags"] intValue] & STACKSHOT_TURNSTILE_STATUS_THREAD) && | |
2233 | [i[@"turnstile_context"] unsignedLongLongValue] == srp_expected_threadid && | |
2234 | srp_expected_threadid != 0) { | |
2235 | found_thread = true; | |
2236 | } | |
2237 | if (([i[@"turnstile_flags"] intValue] & STACKSHOT_TURNSTILE_STATUS_BLOCKED_ON_TASK) && | |
2238 | [i[@"turnstile_context"] intValue] == srp_expected_pid && | |
2239 | srp_expected_pid != -1) { | |
2240 | found_pid = true; | |
2241 | } | |
2242 | if (found_pid || found_thread) { | |
2243 | T_LOG("found SRP %s %lld waiter: %d", (found_thread ? "thread" : "pid"), | |
2244 | [i[@"turnstile_context"] unsignedLongLongValue], [i[@"waiter"] intValue]); | |
2245 | /* we found something that is blocking the correct threadid */ | |
f427ee49 A |
2246 | for (id j in winfos) { |
2247 | if ([j[@"waiter"] intValue] == [i[@"waiter"] intValue] && | |
c3c9b80d | 2248 | [j[@"wait_type"] intValue] == kThreadWaitPortReceive) { |
f427ee49 A |
2249 | found_srp_waitinfo = true; |
2250 | break; | |
2251 | } | |
2252 | } | |
2253 | ||
2254 | if (found_srp_waitinfo) { | |
2255 | break; | |
2256 | } | |
2257 | } | |
2258 | } | |
2259 | } | |
2260 | } | |
d9a64523 | 2261 | |
f427ee49 | 2262 | if (pid != getpid()) { |
d9a64523 A |
2263 | break; |
2264 | } | |
f427ee49 | 2265 | |
d9a64523 | 2266 | T_EXPECT_EQ_STR(current_process_name(), |
f427ee49 | 2267 | [task_snapshot[@"ts_p_comm"] UTF8String], |
d9a64523 A |
2268 | "current process name matches in stackshot"); |
2269 | ||
f427ee49 | 2270 | uint64_t task_flags = [task_snapshot[@"ts_ss_flags"] unsignedLongLongValue]; |
c3c9b80d A |
2271 | T_ASSERT_BITS_NOTSET(task_flags, kTerminatedSnapshot, "current process not marked as terminated"); |
2272 | T_ASSERT_BITS_NOTSET(task_flags, kTaskIsTranslated, "current process not marked as translated"); | |
d9a64523 A |
2273 | |
2274 | T_QUIET; | |
f427ee49 | 2275 | T_EXPECT_LE(pid, [task_snapshot[@"ts_unique_pid"] intValue], |
d9a64523 A |
2276 | "unique pid is greater than pid"); |
2277 | ||
f427ee49 A |
2278 | NSDictionary* task_cpu_architecture = container[@"task_snapshots"][@"task_cpu_architecture"]; |
2279 | T_QUIET; T_ASSERT_NOTNULL(task_cpu_architecture[@"cputype"], "have cputype"); | |
2280 | T_QUIET; T_ASSERT_NOTNULL(task_cpu_architecture[@"cpusubtype"], "have cputype"); | |
2281 | int cputype = [task_cpu_architecture[@"cputype"] intValue]; | |
2282 | int cpusubtype = [task_cpu_architecture[@"cpusubtype"] intValue]; | |
2283 | ||
2284 | struct proc_archinfo archinfo; | |
2285 | int retval = proc_pidinfo(pid, PROC_PIDARCHINFO, 0, &archinfo, sizeof(archinfo)); | |
2286 | T_QUIET; T_WITH_ERRNO; T_ASSERT_GT(retval, 0, "proc_pidinfo(PROC_PIDARCHINFO) returned a value > 0"); | |
2287 | T_QUIET; T_ASSERT_EQ(retval, (int)sizeof(struct proc_archinfo), "proc_pidinfo call for PROC_PIDARCHINFO returned expected size"); | |
2288 | T_QUIET; T_EXPECT_EQ(cputype, archinfo.p_cputype, "cpu type is correct"); | |
2289 | T_QUIET; T_EXPECT_EQ(cpusubtype, archinfo.p_cpusubtype, "cpu subtype is correct"); | |
2290 | ||
d9a64523 | 2291 | bool found_main_thread = false; |
f427ee49 | 2292 | uint64_t main_thread_id = -1ULL; |
c3c9b80d | 2293 | bool found_null_kernel_frame = false; |
d9a64523 A |
2294 | for (id thread_key in container[@"task_snapshots"][@"thread_snapshots"]) { |
2295 | NSMutableDictionary *thread = container[@"task_snapshots"][@"thread_snapshots"][thread_key]; | |
2296 | NSDictionary *thread_snap = thread[@"thread_snapshot"]; | |
2297 | ||
2298 | T_QUIET; T_EXPECT_GT([thread_snap[@"ths_thread_id"] intValue], 0, | |
2299 | "thread ID of thread in current task is valid"); | |
2300 | T_QUIET; T_EXPECT_GT([thread_snap[@"ths_base_priority"] intValue], 0, | |
2301 | "base priority of thread in current task is valid"); | |
2302 | T_QUIET; T_EXPECT_GT([thread_snap[@"ths_sched_priority"] intValue], 0, | |
2303 | "scheduling priority of thread in current task is valid"); | |
2304 | ||
2305 | NSString *pth_name = thread[@"pth_name"]; | |
2306 | if (pth_name != nil && [pth_name isEqualToString:@TEST_THREAD_NAME]) { | |
2307 | found_main_thread = true; | |
f427ee49 | 2308 | main_thread_id = [thread_snap[@"ths_thread_id"] unsignedLongLongValue]; |
d9a64523 A |
2309 | |
2310 | T_QUIET; T_EXPECT_GT([thread_snap[@"ths_total_syscalls"] intValue], 0, | |
2311 | "total syscalls of current thread is valid"); | |
2312 | ||
2313 | NSDictionary *cpu_times = thread[@"cpu_times"]; | |
2314 | T_EXPECT_GE([cpu_times[@"runnable_time"] intValue], | |
2315 | [cpu_times[@"system_time"] intValue] + | |
2316 | [cpu_times[@"user_time"] intValue], | |
2317 | "runnable time of current thread is valid"); | |
2318 | } | |
c3c9b80d A |
2319 | if (!found_null_kernel_frame) { |
2320 | for (NSNumber *frame in thread[@"kernel_frames"]) { | |
2321 | if (frame.unsignedLongValue == 0) { | |
2322 | found_null_kernel_frame = true; | |
2323 | break; | |
2324 | } | |
2325 | } | |
2326 | } | |
d9a64523 A |
2327 | } |
2328 | T_EXPECT_TRUE(found_main_thread, "found main thread for current task in stackshot"); | |
c3c9b80d | 2329 | T_EXPECT_FALSE(found_null_kernel_frame, "should not see any NULL kernel frames"); |
cb323159 A |
2330 | |
2331 | if (expect_turnstile_lock && !found_turnstile_lock) { | |
2332 | NSArray *tsinfos = container[@"task_snapshots"][@"thread_turnstileinfo"]; | |
2333 | ||
2334 | for (id i in tsinfos) { | |
f427ee49 | 2335 | if ([i[@"turnstile_context"] unsignedLongLongValue] == main_thread_id) { |
cb323159 A |
2336 | found_turnstile_lock = true; |
2337 | break; | |
2338 | } | |
2339 | } | |
2340 | } | |
d9a64523 A |
2341 | break; |
2342 | } | |
2343 | case STACKSHOT_KCTYPE_SHAREDCACHE_LOADINFO: { | |
c3c9b80d A |
2344 | struct dyld_shared_cache_loadinfo *payload = kcdata_iter_payload(iter); |
2345 | T_ASSERT_EQ((size_t)kcdata_iter_size(iter), sizeof(*payload), "valid dyld_shared_cache_loadinfo struct"); | |
2346 | ||
2347 | check_shared_cache_uuid(payload->sharedCacheUUID); | |
2348 | ||
2349 | T_ASSERT_LE(payload->sharedCacheUnreliableSlidBaseAddress, | |
2350 | payload->sharedCacheSlidFirstMapping, | |
2351 | "SlidBaseAddress <= SlidFirstMapping"); | |
2352 | T_ASSERT_GE(payload->sharedCacheUnreliableSlidBaseAddress + (1ull << 29), | |
2353 | payload->sharedCacheSlidFirstMapping, | |
2354 | "SlidFirstMapping should be within 512megs of SlidBaseAddress"); | |
f427ee49 | 2355 | |
c3c9b80d A |
2356 | size_t shared_cache_len; |
2357 | const void *addr = _dyld_get_shared_cache_range(&shared_cache_len); | |
2358 | T_ASSERT_EQ((uint64_t)addr, payload->sharedCacheSlidFirstMapping, | |
2359 | "SlidFirstMapping should match shared_cache_range"); | |
f427ee49 A |
2360 | |
2361 | /* | |
2362 | * check_shared_cache_uuid() asserts on failure, so we must have | |
2363 | * found the shared cache UUID to be correct. | |
2364 | */ | |
d9a64523 A |
2365 | found_shared_cache_uuid = true; |
2366 | break; | |
2367 | } | |
2368 | } | |
2369 | } | |
2370 | ||
c3c9b80d A |
2371 | if (expect_sharedcache_child) { |
2372 | T_QUIET; T_ASSERT_TRUE(found_sharedcache_child, "found sharedcache child in kcdata"); | |
2373 | T_QUIET; T_ASSERT_TRUE(found_sharedcache_self, "found self in kcdata"); | |
2374 | if (found_sharedcache_child && found_sharedcache_self) { | |
2375 | T_QUIET; T_ASSERT_NE(sharedcache_child_flags, (uint64_t)kTaskSharedRegionNone, "sharedcache child should have shared region"); | |
2376 | T_QUIET; T_ASSERT_NE(sharedcache_self_flags, (uint64_t)kTaskSharedRegionNone, "sharedcache: self should have shared region"); | |
2377 | if (sharedcache_self_flags == kTaskSharedRegionSystem && !sharedcache_child_sameaddr) { | |
2378 | /* If we're in the system shared region, and the child has a different address, child must have an Other shared region */ | |
2379 | T_ASSERT_EQ(sharedcache_child_flags, (uint64_t)kTaskSharedRegionOther, | |
2380 | "sharedcache child should have Other shared region"); | |
2381 | } | |
2382 | } | |
2383 | } | |
d9a64523 A |
2384 | if (expect_zombie_child) { |
2385 | T_QUIET; T_ASSERT_TRUE(found_zombie_child, "found zombie child in kcdata"); | |
2386 | } | |
2387 | ||
f427ee49 A |
2388 | if (expect_postexec_child) { |
2389 | T_QUIET; T_ASSERT_TRUE(found_postexec_child, "found post-exec child in kcdata"); | |
2390 | } | |
2391 | ||
2392 | if (expect_translated_child) { | |
2393 | T_QUIET; T_ASSERT_TRUE(found_translated_child, "found translated child in kcdata"); | |
2394 | } | |
2395 | ||
d9a64523 A |
2396 | if (expect_shared_cache_layout) { |
2397 | T_QUIET; T_ASSERT_TRUE(found_shared_cache_layout, "shared cache layout found in kcdata"); | |
2398 | } | |
2399 | ||
2400 | if (expect_shared_cache_uuid) { | |
2401 | T_QUIET; T_ASSERT_TRUE(found_shared_cache_uuid, "shared cache UUID found in kcdata"); | |
2402 | } | |
2403 | ||
cb323159 A |
2404 | if (expect_dispatch_queue_label) { |
2405 | T_QUIET; T_ASSERT_TRUE(found_dispatch_queue_label, "dispatch queue label found in kcdata"); | |
2406 | } | |
2407 | ||
2408 | if (expect_turnstile_lock) { | |
2409 | T_QUIET; T_ASSERT_TRUE(found_turnstile_lock, "found expected deadlock"); | |
2410 | } | |
2411 | ||
ea3f0419 A |
2412 | if (expect_cseg_waitinfo) { |
2413 | T_QUIET; T_ASSERT_TRUE(found_cseg_waitinfo, "found c_seg waitinfo"); | |
2414 | } | |
2415 | ||
f427ee49 A |
2416 | if (expect_srp_waitinfo) { |
2417 | T_QUIET; T_ASSERT_TRUE(found_srp_waitinfo, "found special reply port waitinfo"); | |
2418 | } | |
2419 | ||
d9a64523 | 2420 | T_ASSERT_FALSE(KCDATA_ITER_FOREACH_FAILED(iter), "successfully iterated kcdata"); |
f427ee49 A |
2421 | |
2422 | free(inflatedBufferBase); | |
d9a64523 A |
2423 | } |
2424 | ||
2425 | static const char * | |
2426 | current_process_name(void) | |
2427 | { | |
2428 | static char name[64]; | |
2429 | ||
2430 | if (!name[0]) { | |
2431 | int ret = proc_name(getpid(), name, sizeof(name)); | |
2432 | T_QUIET; | |
2433 | T_ASSERT_POSIX_SUCCESS(ret, "proc_name failed for current process"); | |
2434 | } | |
2435 | ||
2436 | return name; | |
2437 | } | |
2438 | ||
2439 | static void | |
2440 | initialize_thread(void) | |
2441 | { | |
2442 | int ret = pthread_setname_np(TEST_THREAD_NAME); | |
2443 | T_QUIET; | |
2444 | T_ASSERT_POSIX_ZERO(ret, "set thread name to %s", TEST_THREAD_NAME); | |
2445 | } |