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
8 // RUN: $SUDO ./dyld_process_info_notify.exe $RUN_DIR/target.exe
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>
24 extern char** environ
;
27 cpu_type_t otherArch
[] = { CPU_TYPE_I386
};
29 cpu_type_t otherArch
[] = { CPU_TYPE_X86_64
};
31 cpu_type_t otherArch
[] = { CPU_TYPE_ARM
};
33 cpu_type_t otherArch
[] = { CPU_TYPE_ARM64
};
41 static struct task_and_pid
launchTest(const char* testProgPath
, const char* arg1
, bool launchOtherArch
, bool launchSuspended
)
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");
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");
55 if ( launchOtherArch
) {
57 if ( posix_spawnattr_setbinpref_np(&attr
, 1, otherArch
, &copied
) != 0 ) {
58 printf("[FAIL] dyld_process_info_notify posix_spawnattr_setbinpref_np()\n");
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
);
70 if (posix_spawnattr_destroy(&attr
) != 0) {
71 printf("[FAIL] dyld_process_info_notify posix_spawnattr_destroy()\n");
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
);
83 static void killTest(struct task_and_pid tp
) {
84 int r
= kill(tp
.pid
, SIGKILL
);
85 waitpid(tp
.pid
, &r
, 0);
88 static void wait_util_task_suspended(task_t task
)
90 struct task_basic_info info
;
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);
96 } while ( info
.suspend_count
== 0 );
100 static bool monitor(struct task_and_pid tp
, bool disconnectEarly
, bool attachLate
)
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;
111 __block
int libFooLoadCount
= 0;
112 __block
int libFooUnloadCount
= 0;
113 dispatch_semaphore_t taskDone
= dispatch_semaphore_create(0);
115 dispatch_queue_t serviceQueue
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH
, 0);
118 dyld_process_info_notify handle
;
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
)
126 if ( strstr(path
, "/libfoo.dylib") != NULL
) {
127 if ( !gotMainNotice
)
128 gotFooNoticeBeforeMain
= true;
133 if ( disconnectEarly
) {
134 gotEarlyNotice
= true;
135 dispatch_semaphore_signal(taskDone
);
140 gotTerminationNotice
= true;
141 dispatch_semaphore_signal(taskDone
);
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) );
149 if ( handle
== NULL
) {
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;
164 // if process suspends itself, wait until it has done so
165 wait_util_task_suspended(tp
.task
);
168 // resume from initial suspend
169 kill(tp
.pid
, SIGCONT
);
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
);
177 fprintf(stderr
, "did not get exit signal\n");
181 // Do not run any tests associated with startup unless the kernel suspended us
184 if ( !sawMainExecutable
) {
185 fprintf(stderr
, "did not get load notification of main executable\n");
189 if ( !gotMainNotice
) {
190 fprintf(stderr
, "did not get notification of main()\n");
194 if ( gotMainNoticeBeforeAllInitialDylibs
) {
195 fprintf(stderr
, "notification of main() arrived before all initial dylibs\n");
199 if ( gotFooNoticeBeforeMain
) {
200 fprintf(stderr
, "notification of main() arrived after libfoo load notice\n");
204 if ( !sawlibSystem
) {
205 fprintf(stderr
, "did not get load notification of libSystem\n");
210 if ( disconnectEarly
) {
211 if ( libFooLoadCount
!= 1 ) {
212 fprintf(stderr
, "got %d load notifications about libFoo instead of 1\n", libFooLoadCount
);
215 if ( libFooUnloadCount
!= 0 ) {
216 fprintf(stderr
, "got %d unload notifications about libFoo instead of 1\n", libFooUnloadCount
);
221 if ( libFooLoadCount
!= 3 ) {
222 fprintf(stderr
, "got %d load notifications about libFoo instead of 3\n", libFooLoadCount
);
225 if ( libFooUnloadCount
!= 3 ) {
226 fprintf(stderr
, "got %d unload notifications about libFoo instead of 3\n", libFooUnloadCount
);
234 static void validateMaxNotifies(struct task_and_pid tp
)
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
) {
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);
251 //fprintf(stderr, "target exited\n");
254 if ( handles
[i
] == NULL
) {
256 // expected failure, because only 8 simultaneous connections allowed
257 // release one and try again
258 _dyld_process_info_notify_release(handles
[4]);
262 fprintf(stderr
, "_dyld_process_info_notify() returned NULL and kern_result=%d, on count=%d\n", kr
, i
);
269 for (int i
=0; i
< 10; ++i
) {
270 if ( handles
[i
] != NULL
) {
271 _dyld_process_info_notify_release(handles
[i
]);
274 dispatch_release(serviceQueue
);
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
) {
289 if ( handle
== NULL
) {
290 fprintf(stderr
, "_dyld_process_info_notify() returned NULL, result=%d\n", kr
);
292 void* h
= dlopen("./libfoo.dylib", 0);
297 int main(int argc
, const char* argv
[])
300 printf("[FAIL] dyld_process_info_notify missing argument\n");
303 const char* testProgPath
= argv
[1];
305 dispatch_async(dispatch_get_main_queue(), ^{
306 struct task_and_pid child
;
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");
317 printf("[PASS] dyld_process_info_notify laucnh suspended (same arch)\n");
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");
329 printf("[PASS] dyld_process_info_notify laucnh suspend-in-main (same arch)\n");
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");
341 printf("[PASS] dyld_process_info_notify laucnh suspended (other arch)\n");
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");
352 printf("[PASS] dyld_process_info_notify laucnh suspend-in-main (other arch)\n");
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");
364 printf("[PASS] dyld_process_info_notify disconnect\n");
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");
371 printf("[PASS] dyld_process_info_notify self-attach\n");