dyld-732.8.tar.gz
[apple/dyld.git] / testing / test-cases / dyld_process_info_notify.dtest / main.c
1
2 // BUILD: $CC target.c -o $BUILD_DIR/target.exe
3 // BUILD: $CC foo.c -o $BUILD_DIR/libfoo.dylib -dynamiclib
4 // BUILD: $CC main.c -o $BUILD_DIR/dyld_process_info_notify.exe
5 // BUILD: $TASK_FOR_PID_ENABLE $BUILD_DIR/dyld_process_info_notify.exe
6
7 // RUN_TIMEOUT: 2400
8 // RUN: $SUDO ./dyld_process_info_notify.exe $RUN_DIR/target.exe
9
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <dlfcn.h>
13 #include <unistd.h>
14 #include <signal.h>
15 #include <spawn.h>
16 #include <errno.h>
17 #include <mach/mach.h>
18 #include <mach/machine.h>
19 #include <mach-o/dyld_process_info.h>
20 #include <dispatch/dispatch.h>
21 #include <Availability.h>
22
23
24 extern char** environ;
25
26 #if __x86_64__
27 cpu_type_t otherArch[] = { CPU_TYPE_I386 };
28 #elif __i386__
29 cpu_type_t otherArch[] = { CPU_TYPE_X86_64 };
30 #elif __arm64__
31 cpu_type_t otherArch[] = { CPU_TYPE_ARM };
32 #elif __arm__
33 cpu_type_t otherArch[] = { CPU_TYPE_ARM64 };
34 #endif
35
36 struct task_and_pid {
37 pid_t pid;
38 task_t task;
39 };
40
41 static struct task_and_pid launchTest(const char* testProgPath, const char* arg1, bool launchOtherArch, bool launchSuspended)
42 {
43 //fprintf(stderr, "launchTest() launchOtherArch=%d, launchSuspended=%d, arg=%s\n", launchOtherArch, launchSuspended, arg1);
44 posix_spawnattr_t attr = 0;
45 if ( posix_spawnattr_init(&attr) != 0 ) {
46 printf("[FAIL] dyld_process_info_notify posix_spawnattr_init()\n");
47 exit(0);
48 }
49 if ( launchSuspended ) {
50 if ( posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED) != 0 ) {
51 printf("[FAIL] dyld_process_info_notify POSIX_SPAWN_START_SUSPENDED\n");
52 exit(0);
53 }
54 }
55 if ( launchOtherArch ) {
56 size_t copied;
57 if ( posix_spawnattr_setbinpref_np(&attr, 1, otherArch, &copied) != 0 ) {
58 printf("[FAIL] dyld_process_info_notify posix_spawnattr_setbinpref_np()\n");
59 exit(0);
60 }
61 }
62
63 struct task_and_pid child;
64 const char* argv[] = { testProgPath, arg1, NULL };
65 int psResult = posix_spawn(&child.pid, testProgPath, NULL, &attr, (char**)argv, environ);
66 if ( psResult != 0 ) {
67 printf("[FAIL] dyld_process_info_notify posix_spawn(%s) failed, err=%d\n", testProgPath, psResult);
68 exit(0);
69 }
70 if (posix_spawnattr_destroy(&attr) != 0) {
71 printf("[FAIL] dyld_process_info_notify posix_spawnattr_destroy()\n");
72 exit(0);
73 }
74 if ( task_for_pid(mach_task_self(), child.pid, &child.task) != KERN_SUCCESS ) {
75 printf("[FAIL] dyld_process_info_notify task_for_pid()\n");
76 (void)kill(child.pid, SIGKILL);
77 exit(0);
78 }
79
80 return child;
81 }
82
83 static void killTest(struct task_and_pid tp) {
84 int r = kill(tp.pid, SIGKILL);
85 waitpid(tp.pid, &r, 0);
86 }
87
88 static void wait_util_task_suspended(task_t task)
89 {
90 struct task_basic_info info;
91 do {
92 unsigned count = TASK_BASIC_INFO_COUNT;
93 kern_return_t kr = task_info(task, TASK_BASIC_INFO, (task_info_t)&info, &count);
94 //fprintf(stderr, "task_info() => %d, suspendCount=%d\n", kr, info.suspend_count);
95 sleep(1);
96 } while ( info.suspend_count == 0 );
97 }
98
99
100 static bool monitor(struct task_and_pid tp, bool disconnectEarly, bool attachLate)
101 {
102 kern_return_t kr;
103 __block bool sawMainExecutable = false;
104 __block bool sawlibSystem = false;
105 __block bool gotTerminationNotice = false;
106 __block bool gotEarlyNotice = false;
107 __block bool gotMainNotice = false;
108 __block bool gotMainNoticeBeforeAllInitialDylibs = false;
109 __block bool gotFooNoticeBeforeMain = false;
110
111 __block int libFooLoadCount = 0;
112 __block int libFooUnloadCount = 0;
113 dispatch_semaphore_t taskDone = dispatch_semaphore_create(0);
114
115 dispatch_queue_t serviceQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
116
117 unsigned count = 0;
118 dyld_process_info_notify handle;
119 do {
120 handle = _dyld_process_info_notify(tp.task, serviceQueue,
121 ^(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path) {
122 if ( strstr(path, "/target.exe") != NULL )
123 sawMainExecutable = true;
124 if ( strstr(path, "/libSystem") != NULL )
125 sawlibSystem = true;
126 if ( strstr(path, "/libfoo.dylib") != NULL ) {
127 if ( !gotMainNotice )
128 gotFooNoticeBeforeMain = true;
129 if ( unload )
130 ++libFooUnloadCount;
131 else
132 ++libFooLoadCount;
133 if ( disconnectEarly ) {
134 gotEarlyNotice = true;
135 dispatch_semaphore_signal(taskDone);
136 }
137 }
138 },
139 ^{
140 gotTerminationNotice = true;
141 dispatch_semaphore_signal(taskDone);
142 },
143 &kr);
144 ++count;
145 if ( handle == NULL )
146 fprintf(stderr, "_dyld_process_info_notify() returned NULL, result=%d, count=%d\n", kr, count);
147 } while ( (handle == NULL) && (count < 5) );
148
149 if ( handle == NULL ) {
150 return false;
151 }
152
153 if (!attachLate) {
154 // If the process starts suspended register for main(),
155 // otherwise skip since this test is a race between
156 // process setup and notification registration
157 _dyld_process_info_notify_main(handle, ^{
158 //fprintf(stderr, "target entering main()\n");
159 gotMainNotice = true;
160 if ( !sawMainExecutable || !sawlibSystem )
161 gotMainNoticeBeforeAllInitialDylibs = true;
162 });
163 } else {
164 // if process suspends itself, wait until it has done so
165 wait_util_task_suspended(tp.task);
166 }
167
168 // resume from initial suspend
169 kill(tp.pid, SIGCONT);
170
171 // block waiting for notification that target has exited
172 bool gotSignal = (dispatch_semaphore_wait(taskDone, dispatch_time(DISPATCH_TIME_NOW, 10LL * NSEC_PER_SEC)) == 0);
173 _dyld_process_info_notify_release(handle);
174
175
176 if ( !gotSignal ) {
177 fprintf(stderr, "did not get exit signal\n");
178 return false;
179 }
180
181 // Do not run any tests associated with startup unless the kernel suspended us
182 // before main()
183 if (!attachLate) {
184 if ( !sawMainExecutable ) {
185 fprintf(stderr, "did not get load notification of main executable\n");
186 return false;
187 }
188
189 if ( !gotMainNotice ) {
190 fprintf(stderr, "did not get notification of main()\n");
191 return false;
192 }
193
194 if ( gotMainNoticeBeforeAllInitialDylibs ) {
195 fprintf(stderr, "notification of main() arrived before all initial dylibs\n");
196 return false;
197 }
198
199 if ( gotFooNoticeBeforeMain ) {
200 fprintf(stderr, "notification of main() arrived after libfoo load notice\n");
201 return false;
202 }
203
204 if ( !sawlibSystem ) {
205 fprintf(stderr, "did not get load notification of libSystem\n");
206 return false;
207 }
208 }
209
210 if ( disconnectEarly ) {
211 if ( libFooLoadCount != 1 ) {
212 fprintf(stderr, "got %d load notifications about libFoo instead of 1\n", libFooLoadCount);
213 return false;
214 }
215 if ( libFooUnloadCount != 0 ) {
216 fprintf(stderr, "got %d unload notifications about libFoo instead of 1\n", libFooUnloadCount);
217 return false;
218 }
219 }
220 else {
221 if ( libFooLoadCount != 3 ) {
222 fprintf(stderr, "got %d load notifications about libFoo instead of 3\n", libFooLoadCount);
223 return false;
224 }
225 if ( libFooUnloadCount != 3 ) {
226 fprintf(stderr, "got %d unload notifications about libFoo instead of 3\n", libFooUnloadCount);
227 return false;
228 }
229 }
230
231 return true;
232 }
233
234 static void validateMaxNotifies(struct task_and_pid tp)
235 {
236 dispatch_queue_t serviceQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
237 dyld_process_info_notify handles[10];
238 // This loop goes through 10 iterations
239 // i = 0..7 Should succeed
240 // i = 8 Should fail, but trigger a release that frees up a slot
241 // i = 9 Should succeed
242 for (int i=0; i < 10; ++i) {
243 kern_return_t kr;
244 handles[i] = _dyld_process_info_notify(tp.task, serviceQueue,
245 ^(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path) {
246 //fprintf(stderr, "unload=%d, 0x%012llX <%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X> %s\n",
247 // unload, machHeader, uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7],
248 // uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15], path);
249 },
250 ^{
251 //fprintf(stderr, "target exited\n");
252 },
253 &kr);
254 if ( handles[i] == NULL ) {
255 if ( i == 8 ) {
256 // expected failure, because only 8 simultaneous connections allowed
257 // release one and try again
258 _dyld_process_info_notify_release(handles[4]);
259 handles[4] = NULL;
260 }
261 else {
262 fprintf(stderr, "_dyld_process_info_notify() returned NULL and kern_result=%d, on count=%d\n", kr, i);
263 killTest(tp);
264 exit(0);
265 }
266 }
267 }
268 // release all
269 for (int i=0; i < 10; ++i) {
270 if ( handles[i] != NULL ) {
271 _dyld_process_info_notify_release(handles[i]);
272 }
273 }
274 dispatch_release(serviceQueue);
275 }
276
277 static bool testSelfAttach(void) {
278 __block bool retval = false;
279 kern_return_t kr = KERN_SUCCESS;
280 dispatch_queue_t serviceQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
281 dyld_process_info_notify handle = _dyld_process_info_notify(mach_task_self(), serviceQueue,
282 ^(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path) {
283 if ( strstr(path, "/libfoo.dylib") != NULL ) {
284 retval = true;
285 }
286 },
287 ^{},
288 &kr);
289 if ( handle == NULL ) {
290 fprintf(stderr, "_dyld_process_info_notify() returned NULL, result=%d\n", kr);
291 }
292 void* h = dlopen("./libfoo.dylib", 0);
293 dlclose(h);
294 return retval;
295 }
296
297 int main(int argc, const char* argv[])
298 {
299 if ( argc < 2 ) {
300 printf("[FAIL] dyld_process_info_notify missing argument\n");
301 exit(0);
302 }
303 const char* testProgPath = argv[1];
304
305 dispatch_async(dispatch_get_main_queue(), ^{
306 struct task_and_pid child;
307
308 // test 1) launch test program suspended in same arch as this program
309 printf("[BEGIN] dyld_process_info_notify laucnh suspended (same arch)\n");
310 child = launchTest(testProgPath, "", false, true);
311 if ( ! monitor(child, false, false) ) {
312 printf("[FAIL] dyld_process_info_notify launch suspended missed some notifications\n");
313 killTest(child);
314 exit(0);
315 }
316 killTest(child);
317 printf("[PASS] dyld_process_info_notify laucnh suspended (same arch)\n");
318
319 // test 2) launch test program in same arch as this program where it sleeps itself
320 printf("[BEGIN] dyld_process_info_notify laucnh suspend-in-main (same arch)\n");
321 child = launchTest(testProgPath, "suspend-in-main", false, false);
322 validateMaxNotifies(child);
323 if ( ! monitor(child, false, true) ) {
324 printf("[FAIL] dyld_process_info_notify launch suspend-in-main missed some notifications\n");
325 killTest(child);
326 exit(0);
327 }
328 killTest(child);
329 printf("[PASS] dyld_process_info_notify laucnh suspend-in-main (same arch)\n");
330
331 #if 0
332 // test 3) launch test program suspended in opposite arch as this program
333 printf("[BEGIN] dyld_process_info_notify laucnh suspended (other arch)\n");
334 child = launchTest(testProgPath, "", true, true);
335 if ( ! monitor(child, false, false) ) {
336 printf("[FAIL] dyld_process_info_notify launch suspended other arch missed some notifications\n");
337 killTest(child);
338 exit(0);
339 }
340 killTest(child);
341 printf("[PASS] dyld_process_info_notify laucnh suspended (other arch)\n");
342
343 // test 4) launch test program in opposite arch as this program where it sleeps itself
344 printf("[BEGIN] dyld_process_info_notify laucnh suspend-in-main (other arch)\n");
345 child = launchTest(testProgPath, "suspend-in-main", true, false);
346 if ( ! monitor(child, false, true) ) {
347 printf("[FAIL] dyld_process_info_notify launch other arch suspend-in-main missed some notifications\n");
348 killTest(child);
349 exit(0);
350 }
351 killTest(child);
352 printf("[PASS] dyld_process_info_notify laucnh suspend-in-main (other arch)\n");
353 #endif
354
355 // test 5) launch test program where we disconnect from it after first dlopen
356 printf("[BEGIN] dyld_process_info_notify disconnect\n");
357 child = launchTest(testProgPath, "", false, true);
358 if ( ! monitor(child, true, false) ) {
359 printf("[FAIL] dyld_process_info_notify connect/disconnect missed some notifications\n");
360 killTest(child);
361 exit(0);
362 }
363 killTest(child);
364 printf("[PASS] dyld_process_info_notify disconnect\n");
365
366 // test 6) attempt to monitor the monitoring process
367 printf("[BEGIN] dyld_process_info_notify self-attach\n");
368 if (! testSelfAttach() ) {
369 printf("[FAIL] dyld_process_info_notify self notification\n");
370 }
371 printf("[PASS] dyld_process_info_notify self-attach\n");
372
373 exit(0);
374 });
375
376 dispatch_main();
377 }