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