1 #include <mach-o/dyld.h>
2 #include <spawn.h>
3 #include <spawn_private.h>
4 #include <stdlib.h>
5 #include <sys/spawn_internal.h>
6 #include <sys/sysctl.h>
7
8 #include <darwintest.h>
9 #include <darwintest_utils.h>
10
11 T_GLOBAL_META(
12 T_META_NAMESPACE("xnu.vm"),
13 T_META_RADAR_COMPONENT_NAME("xnu"),
14 T_META_RADAR_COMPONENT_VERSION("VM"));
15
16 static void
set_small_relaunch_values(posix_spawnattr_t * attrs)17 set_small_relaunch_values(posix_spawnattr_t *attrs)
18 {
19 static const uint32_t kNumRelaunchValues = 16;
20 static uint32_t relaunch_values[kNumRelaunchValues] = {0};
21 int ret;
22 /*
23 * Set the relaunch times to very small values (in m.s.).
24 * Everything under 5 seconds is expected to fall in the high relaunch behavior bucket.
25 */
26 for (uint32_t i = 0; i < kNumRelaunchValues; i++) {
27 relaunch_values[i] = i;
28 }
29 ret = posix_spawnattr_set_jetsam_ttr_np(attrs, kNumRelaunchValues, relaunch_values);
30 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "posix_spawnattr_set_jetsam_ttr_np");
31 }
32
33 T_DECL(set_high_relaunch_behavior, "supply very small time to relaunch values", T_META_TAG_VM_PREFERRED)
34 {
35 posix_spawnattr_t attrs;
36 _posix_spawnattr_t psattr;
37 uint32_t relaunch_flags = 0;
38
39 posix_spawnattr_init(&attrs);
40 set_small_relaunch_values(&attrs);
41
42 psattr = *(_posix_spawnattr_t *)&attrs;
43
44 relaunch_flags = psattr->psa_jetsam_flags & POSIX_SPAWN_JETSAM_RELAUNCH_BEHAVIOR_MASK;
45 T_QUIET; T_ASSERT_EQ(relaunch_flags, POSIX_SPAWN_JETSAM_RELAUNCH_BEHAVIOR_HIGH, "relaunch behavior is high");
46
47 posix_spawnattr_destroy(&attrs);
48 }
49
50 T_DECL(set_medium_relaunch_behavior, "supply very large time to relaunch values", T_META_TAG_VM_PREFERRED)
51 {
52 posix_spawnattr_t attrs;
53 _posix_spawnattr_t psattr;
54 int ret;
55 static const uint32_t kNumRelaunchValues = 16;
56 static uint32_t relaunch_values[kNumRelaunchValues] = {0};
57 uint32_t relaunch_flags = 0;
58
59 posix_spawnattr_init(&attrs);
60
61 /*
62 * Set the relaunch times to medium large values (in m.s.).
63 * Everything over between 5 and 10 seconds is expected to fall in the medium relaunch behavior bucket.
64 */
65 for (uint32_t i = 0; i < kNumRelaunchValues; i++) {
66 relaunch_values[i] = 5000 + i;
67 }
68 ret = posix_spawnattr_set_jetsam_ttr_np(&attrs, kNumRelaunchValues, relaunch_values);
69 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "posix_spawnattr_set_jetsam_ttr_np");
70
71 psattr = *(_posix_spawnattr_t *)&attrs;
72
73 relaunch_flags = psattr->psa_jetsam_flags & POSIX_SPAWN_JETSAM_RELAUNCH_BEHAVIOR_MASK;
74 T_QUIET; T_ASSERT_EQ(relaunch_flags, POSIX_SPAWN_JETSAM_RELAUNCH_BEHAVIOR_MED, "relaunch behavior is medium");
75
76 posix_spawnattr_destroy(&attrs);
77 }
78
79
80 T_DECL(set_low_relaunch_behavior, "supply very large time to relaunch values", T_META_TAG_VM_PREFERRED)
81 {
82 posix_spawnattr_t attrs;
83 _posix_spawnattr_t psattr;
84 int ret;
85 static const uint32_t kNumRelaunchValues = 16;
86 static uint32_t relaunch_values[kNumRelaunchValues] = {0};
87 uint32_t relaunch_flags = 0;
88
89 posix_spawnattr_init(&attrs);
90
91 /*
92 * Set the relaunch times to very large values (in m.s.).
93 * Everything over 10 seconds is expected to fall in the low relaunch behavior bucket.
94 */
95 for (uint32_t i = 0; i < kNumRelaunchValues; i++) {
96 relaunch_values[i] = 10000 + i;
97 }
98 ret = posix_spawnattr_set_jetsam_ttr_np(&attrs, kNumRelaunchValues, relaunch_values);
99 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "posix_spawnattr_set_jetsam_ttr_np");
100
101 psattr = *(_posix_spawnattr_t *)&attrs;
102
103 relaunch_flags = psattr->psa_jetsam_flags & POSIX_SPAWN_JETSAM_RELAUNCH_BEHAVIOR_MASK;
104 T_QUIET; T_ASSERT_EQ(relaunch_flags, POSIX_SPAWN_JETSAM_RELAUNCH_BEHAVIOR_LOW, "relaunch behavior is low");
105
106 posix_spawnattr_destroy(&attrs);
107 }
108
109 T_DECL(set_high_relaunch_with_mixed_histogram, "supply slightly more small values than large values", T_META_TAG_VM_PREFERRED)
110 {
111 posix_spawnattr_t attrs;
112 _posix_spawnattr_t psattr;
113 int ret;
114 static const uint32_t kNumRelaunchValues = 16;
115 static uint32_t relaunch_values[kNumRelaunchValues] = {0};
116 uint32_t relaunch_flags = 0;
117
118 posix_spawnattr_init(&attrs);
119
120 /*
121 * Make sure the high likelihood bucket (<5 seconds) is a bit larger than the others
122 */
123 for (uint32_t i = 0; i < kNumRelaunchValues; i++) {
124 if (i % 2 == 0) {
125 relaunch_values[i] = i;
126 } else {
127 if (i % 3 == 0) {
128 relaunch_values[i] = 10000 + i;
129 } else {
130 relaunch_values[i] = 5000 + i;
131 }
132 }
133 }
134 ret = posix_spawnattr_set_jetsam_ttr_np(&attrs, kNumRelaunchValues, relaunch_values);
135 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "posix_spawnattr_set_jetsam_ttr_np");
136
137 psattr = *(_posix_spawnattr_t *)&attrs;
138
139 relaunch_flags = psattr->psa_jetsam_flags & POSIX_SPAWN_JETSAM_RELAUNCH_BEHAVIOR_MASK;
140 T_QUIET; T_ASSERT_EQ(relaunch_flags, POSIX_SPAWN_JETSAM_RELAUNCH_BEHAVIOR_HIGH, "relaunch behavior is high");
141
142 posix_spawnattr_destroy(&attrs);
143 }
144
145 extern char **environ;
146 T_HELPER_DECL(check_relaunch_flags, "Check that we have the high relaunch likelihood flag set")
147 {
148 int relaunch_flags;
149 size_t size = sizeof(relaunch_flags);
150 int ret = sysctlbyname("kern.memorystatus_relaunch_flags", &relaunch_flags, &size, NULL, 0);
151 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query vm.pagesize");
152 T_QUIET; T_ASSERT_EQ(relaunch_flags, POSIX_SPAWN_JETSAM_RELAUNCH_BEHAVIOR_HIGH, "relaunch behavior is high");
153 }
154
155 T_HELPER_DECL(exec_into_check_relaunch_flags, "Do an exec into check_relaunch_flags")
156 {
157 posix_spawnattr_t attrs;
158 int ret;
159 char testpath[PATH_MAX];
160 uint32_t testpath_buf_size;
161 char **arguments;
162
163 testpath_buf_size = sizeof(testpath);
164 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
165 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
166
167 arguments = (char *[]) {
168 testpath,
169 "-n",
170 "check_relaunch_flags",
171 NULL
172 };
173 posix_spawnattr_init(&attrs);
174 ret = posix_spawnattr_setflags(&attrs, POSIX_SPAWN_SETEXEC);
175 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix spawn set exec flag");
176 ret = posix_spawn(NULL, testpath, NULL, &attrs, arguments, environ);
177 T_FAIL("posix_spawn failed with %s\n", strerror(ret));
178 }
179
180 static void
posix_spawn_helper_and_wait_for_exit(char * name)181 posix_spawn_helper_and_wait_for_exit(char *name)
182 {
183 posix_spawnattr_t attrs;
184 int ret;
185 pid_t child_pid;
186 char testpath[PATH_MAX];
187 uint32_t testpath_buf_size;
188 char **arguments;
189
190 testpath_buf_size = sizeof(testpath);
191 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
192 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
193
194 posix_spawnattr_init(&attrs);
195 set_small_relaunch_values(&attrs);
196
197 arguments = (char *[]) {
198 testpath,
199 "-n",
200 name,
201 NULL
202 };
203
204 ret = posix_spawn(&child_pid, testpath, NULL, &attrs, arguments, environ);
205
206 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "posix_spawn");
207
208 while (true) {
209 int status;
210 pid_t rc = waitpid(child_pid, &status, 0);
211 if (rc == -1 && errno == EINTR) {
212 continue;
213 }
214 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
215 T_QUIET; T_ASSERT_TRUE(WIFEXITED(status), "Exited cleanly");
216 T_QUIET; T_ASSERT_EQ(WEXITSTATUS(status), 0, "return code was 0");
217 break;
218 }
219
220 posix_spawnattr_destroy(&attrs);
221 }
222
223 T_DECL(posix_spawn_sets_relaunch_flags, "Check that posix_spawn sets the relaunch flags on the new proc", T_META_TAG_VM_PREFERRED)
224 {
225 posix_spawn_helper_and_wait_for_exit("check_relaunch_flags");
226 }
227
228 T_DECL(relaunch_flags_persist_across_exec, "Check that the relaunch flags persist across exec", T_META_TAG_VM_PREFERRED)
229 {
230 posix_spawn_helper_and_wait_for_exit("exec_into_check_relaunch_flags");
231 }
232