+#define CACHEADDR_ENV "STACKSHOT_TEST_DYLDADDR"
+T_HELPER_DECL(spawn_reslide_child, "child process to spawn with alternate slide")
+{
+ size_t shared_cache_len;
+ const void *addr, *prevaddr;
+ uintmax_t v;
+ char *endptr;
+
+ const char *cacheaddr_env = getenv(CACHEADDR_ENV);
+ T_QUIET; T_ASSERT_NOTNULL(cacheaddr_env, "getenv("CACHEADDR_ENV")");
+ errno = 0;
+ endptr = NULL;
+ v = strtoumax(cacheaddr_env, &endptr, 16); /* read hex value */
+ T_WITH_ERRNO; T_QUIET; T_ASSERT_NE(v, 0l, "getenv(%s) = \"%s\" should be a non-zero hex number", CACHEADDR_ENV, cacheaddr_env);
+ T_QUIET; T_ASSERT_EQ(*endptr, 0, "getenv(%s) = \"%s\" endptr \"%s\" should be empty", CACHEADDR_ENV, cacheaddr_env, endptr);
+
+ prevaddr = (const void *)v;
+ addr = _dyld_get_shared_cache_range(&shared_cache_len);
+ T_QUIET; T_ASSERT_NOTNULL(addr, "shared cache address");
+
+ T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(getppid(), (addr == prevaddr) ? SIGUSR2 : SIGUSR1), "signaled parent to take stackshot");
+ for (;;) {
+ (void) pause(); /* parent will kill -9 us */
+ }
+}
+
+T_DECL(shared_cache_flags, "tests stackshot's task_ss_flags for the shared cache")
+{
+ posix_spawnattr_t attr;
+ char *env_addr;
+ char path[PATH_MAX];
+ __block bool child_same_addr = false;
+
+ uint32_t path_size = sizeof(path);
+ T_QUIET; T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath");
+ char *args[] = { path, "-n", "spawn_reslide_child", NULL };
+ pid_t pid;
+ size_t shared_cache_len;
+ const void *addr;
+
+ dispatch_source_t child_diffsig_src, child_samesig_src;
+ dispatch_semaphore_t child_ready_sem = dispatch_semaphore_create(0);
+ T_QUIET; T_ASSERT_NOTNULL(child_ready_sem, "shared_cache child semaphore");
+
+ dispatch_queue_t signal_processing_q = dispatch_queue_create("signal processing queue", NULL);
+ T_QUIET; T_ASSERT_NOTNULL(signal_processing_q, "signal processing queue");
+
+ signal(SIGUSR1, SIG_IGN);
+ signal(SIGUSR2, SIG_IGN);
+ child_samesig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, signal_processing_q);
+ T_QUIET; T_ASSERT_NOTNULL(child_samesig_src, "dispatch_source_create (child_samesig_src)");
+ child_diffsig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR2, 0, signal_processing_q);
+ T_QUIET; T_ASSERT_NOTNULL(child_diffsig_src, "dispatch_source_create (child_diffsig_src)");
+
+ /* child will signal us depending on if their addr is the same or different */
+ dispatch_source_set_event_handler(child_samesig_src, ^{ child_same_addr = false; dispatch_semaphore_signal(child_ready_sem); });
+ dispatch_source_set_event_handler(child_diffsig_src, ^{ child_same_addr = true; dispatch_semaphore_signal(child_ready_sem); });
+ dispatch_activate(child_samesig_src);
+ dispatch_activate(child_diffsig_src);
+
+ addr = _dyld_get_shared_cache_range(&shared_cache_len);
+ T_QUIET; T_ASSERT_NOTNULL(addr, "shared cache address");
+
+ T_QUIET; T_ASSERT_POSIX_SUCCESS(asprintf(&env_addr, "%p", addr), "asprintf of env_addr succeeded");
+ T_QUIET; T_ASSERT_POSIX_SUCCESS(setenv(CACHEADDR_ENV, env_addr, true), "setting "CACHEADDR_ENV" to %s", env_addr);
+
+ T_QUIET; T_ASSERT_POSIX_ZERO(posix_spawnattr_init(&attr), "posix_spawnattr_init");
+ T_QUIET; T_ASSERT_POSIX_ZERO(posix_spawnattr_setflags(&attr, _POSIX_SPAWN_RESLIDE), "posix_spawnattr_setflags");
+ int sp_ret = posix_spawn(&pid, path, NULL, &attr, args, environ);
+ T_ASSERT_POSIX_ZERO(sp_ret, "spawned process '%s' with PID %d", args[0], pid);
+
+ dispatch_semaphore_wait(child_ready_sem, DISPATCH_TIME_FOREVER);
+ T_LOG("received signal from child (%s), capturing stackshot", child_same_addr ? "same shared cache addr" : "different shared cache addr");
+
+ struct scenario scenario = {
+ .name = "shared_cache_flags",
+ .flags = (STACKSHOT_SAVE_LOADINFO | STACKSHOT_GET_GLOBAL_MEM_STATS
+ | STACKSHOT_COLLECT_SHAREDCACHE_LAYOUT
+ | STACKSHOT_SAVE_IMP_DONATION_PIDS | STACKSHOT_KCDATA_FORMAT),
+ };
+
+ take_stackshot(&scenario, false, ^( void *ssbuf, size_t sslen) {
+ int status;
+ /* First kill the child so we can reap it */
+ T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(pid, SIGKILL), "killing spawned process");
+ T_QUIET; T_ASSERT_POSIX_SUCCESS(waitpid(pid, &status, 0), "waitpid on spawned child");
+ T_QUIET; T_ASSERT_EQ(!!WIFSIGNALED(status), 1, "waitpid status should be signalled");
+ T_QUIET; T_ASSERT_EQ(WTERMSIG(status), SIGKILL, "waitpid status should be SIGKILLed");
+
+ parse_stackshot(PARSE_STACKSHOT_SHAREDCACHE_FLAGS, ssbuf, sslen,
+ @{sharedcache_child_pid_key: @(pid), sharedcache_child_sameaddr_key: @(child_same_addr ? 1 : 0)});
+ });
+}
+