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>
23 extern char** environ
;
26 cpu_type_t otherArch
[] = { CPU_TYPE_I386
};
28 cpu_type_t otherArch
[] = { CPU_TYPE_X86_64
};
30 cpu_type_t otherArch
[] = { CPU_TYPE_ARM
};
32 cpu_type_t otherArch
[] = { CPU_TYPE_ARM64
};
35 static task_t
launchTest(const char* testProgPath
, const char* arg1
, bool launchOtherArch
, bool launchSuspended
)
37 //fprintf(stderr, "launchTest() launchOtherArch=%d, launchSuspended=%d, arg=%s\n", launchOtherArch, launchSuspended, arg1);
38 posix_spawnattr_t attr
;
39 if ( posix_spawnattr_init(&attr
) != 0 ) {
40 printf("[FAIL] dyld_process_info_notify posix_spawnattr_init()\n");
43 if ( launchSuspended
) {
44 if ( posix_spawnattr_setflags(&attr
, POSIX_SPAWN_START_SUSPENDED
) != 0 ) {
45 printf("[FAIL] dyld_process_info_notify POSIX_SPAWN_START_SUSPENDED\n");
49 if ( launchOtherArch
) {
51 if ( posix_spawnattr_setbinpref_np(&attr
, 1, otherArch
, &copied
) != 0 ) {
52 printf("[FAIL] dyld_process_info_notify posix_spawnattr_setbinpref_np()\n");
58 const char* argv
[] = { testProgPath
, arg1
, NULL
};
59 int psResult
= posix_spawn(&childPid
, testProgPath
, NULL
, &attr
, (char**)argv
, environ
);
60 if ( psResult
!= 0 ) {
61 printf("[FAIL] dyld_process_info_notify posix_spawn(%s) failed, err=%d\n", testProgPath
, psResult
);
64 //printf("child pid=%d\n", childPid);
67 if ( task_for_pid(mach_task_self(), childPid
, &childTask
) != KERN_SUCCESS
) {
68 printf("[FAIL] dyld_process_info_notify task_for_pid()\n");
69 kill(childPid
, SIGKILL
);
76 static void wait_util_task_suspended(task_t task
)
78 struct task_basic_info info
;
80 unsigned count
= TASK_BASIC_INFO_COUNT
;
81 kern_return_t kr
= task_info(task
, TASK_BASIC_INFO
, (task_info_t
)&info
, &count
);
82 //fprintf(stderr, "task_info() => %d, suspendCount=%d\n", kr, info.suspend_count);
84 } while ( info
.suspend_count
== 0 );
88 static bool monitor(task_t task
, bool disconnectEarly
, bool attachLate
)
91 __block
bool sawMainExecutable
= false;
92 __block
bool sawlibSystem
= false;
93 __block
bool gotTerminationNotice
= false;
94 __block
bool gotEarlyNotice
= false;
95 __block
int libFooLoadCount
= 0;
96 __block
int libFooUnloadCount
= 0;
97 dispatch_semaphore_t taskDone
= dispatch_semaphore_create(0);
99 dispatch_queue_t serviceQueue
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH
, 0);
102 dyld_process_info_notify handle
;
104 handle
= _dyld_process_info_notify(task
, serviceQueue
,
105 ^(bool unload
, uint64_t timestamp
, uint64_t machHeader
, const uuid_t uuid
, const char* path
) {
106 if ( strstr(path
, "/target.exe") != NULL
)
107 sawMainExecutable
= true;
108 if ( strstr(path
, "/libSystem") != NULL
)
110 if ( strstr(path
, "/libfoo.dylib") != NULL
) {
115 if ( disconnectEarly
) {
116 gotEarlyNotice
= true;
117 dispatch_semaphore_signal(taskDone
);
120 //fprintf(stderr, "unload=%d, 0x%012llX <%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X> %s\n",
121 // unload, machHeader, uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7],
122 // uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15], path);
125 //fprintf(stderr, "target exited\n");
126 gotTerminationNotice
= true;
127 dispatch_semaphore_signal(taskDone
);
131 if ( handle
== NULL
)
132 fprintf(stderr
, "_dyld_process_info_notify() returned NULL, result=%d, count=%d\n", kr
, count
);
133 } while ( (handle
== NULL
) && (count
< 5) );
135 if ( handle
== NULL
) {
139 // if process suspends itself, wait until it has done so
141 wait_util_task_suspended(task
);
143 // resume from initial suspend
146 // block waiting for notification that target has exited
147 bool gotSignal
= (dispatch_semaphore_wait(taskDone
, dispatch_time(DISPATCH_TIME_NOW
, 10LL * NSEC_PER_SEC
)) == 0);
148 _dyld_process_info_notify_release(handle
);
152 fprintf(stderr
, "did not get exit signal\n");
156 if ( !attachLate
&& !sawMainExecutable
) {
157 fprintf(stderr
, "did not get load notification of main executable\n");
161 if ( !attachLate
&& !sawlibSystem
) {
162 fprintf(stderr
, "did not get load notification of libSystem\n");
166 if ( disconnectEarly
) {
167 if ( libFooLoadCount
!= 1 ) {
168 fprintf(stderr
, "got %d load notifications about libFoo instead of 1\n", libFooLoadCount
);
171 if ( libFooUnloadCount
!= 0 ) {
172 fprintf(stderr
, "got %d unload notifications about libFoo instead of 1\n", libFooUnloadCount
);
177 if ( libFooLoadCount
!= 3 ) {
178 fprintf(stderr
, "got %d load notifications about libFoo instead of 3\n", libFooLoadCount
);
181 if ( libFooUnloadCount
!= 3 ) {
182 fprintf(stderr
, "got %d unload notifications about libFoo instead of 3\n", libFooUnloadCount
);
190 static void validateMaxNotifies(task_t task
)
192 dispatch_queue_t serviceQueue
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH
, 0);
193 dyld_process_info_notify handles
[10];
194 for (int i
=0; i
< 10; ++i
) {
196 handles
[i
] = _dyld_process_info_notify(task
, serviceQueue
,
197 ^(bool unload
, uint64_t timestamp
, uint64_t machHeader
, const uuid_t uuid
, const char* path
) {
198 //fprintf(stderr, "unload=%d, 0x%012llX <%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X> %s\n",
199 // unload, machHeader, uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7],
200 // uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15], path);
203 //fprintf(stderr, "target exited\n");
206 if ( handles
[i
] == NULL
) {
208 // expected failure, because only 8 simultaneous connections allowed
209 // release one and try again
210 _dyld_process_info_notify_release(handles
[4]);
214 fprintf(stderr
, "_dyld_process_info_notify() returned NULL and kern_result=%d, on count=%d\n", kr
, i
);
215 task_terminate(task
);
221 for (int i
=0; i
< 10; ++i
) {
222 if ( handles
[i
] != NULL
) {
223 _dyld_process_info_notify_release(handles
[i
]);
226 dispatch_release(serviceQueue
);
229 int main(int argc
, const char* argv
[])
231 printf("[BEGIN] dyld_process_info_notify\n");
233 printf("[FAIL] dyld_process_info_notify missing argument\n");
236 const char* testProgPath
= argv
[1];
238 dispatch_async(dispatch_get_main_queue(), ^{
241 // test 1) launch test program suspended in same arch as this program
242 childTask
= launchTest(testProgPath
, "", false, true);
243 if ( ! monitor(childTask
, false, false) ) {
244 printf("[FAIL] dyld_process_info_notify launch suspended missed some notifications\n");
245 task_terminate(childTask
);
248 task_terminate(childTask
);
250 // test 2) launch test program in same arch as this program where it sleeps itself
251 childTask
= launchTest(testProgPath
, "suspend-in-main", false, false);
252 validateMaxNotifies(childTask
);
253 if ( ! monitor(childTask
, false, true) ) {
254 printf("[FAIL] dyld_process_info_notify launch suspend-in-main missed some notifications\n");
255 task_terminate(childTask
);
258 task_terminate(childTask
);
260 #if !TARGET_OS_WATCH && !TARGET_OS_TV && __LP64__
261 // test 3) launch test program suspended in opposite arch as this program
262 childTask
= launchTest(testProgPath
, "", true, true);
263 if ( ! monitor(childTask
, false, false) ) {
264 printf("[FAIL] dyld_process_info_notify launch suspended other arch missed some notifications\n");
265 task_terminate(childTask
);
268 task_terminate(childTask
);
270 // test 4) launch test program in opposite arch as this program where it sleeps itself
271 childTask
= launchTest(testProgPath
, "suspend-in-main", true, false);
272 if ( ! monitor(childTask
, false, true) ) {
273 printf("[FAIL] dyld_process_info_notify launch other arch suspend-in-main missed some notifications\n");
274 task_terminate(childTask
);
277 task_terminate(childTask
);
280 // test 5) launch test program where we disconnect from it after first dlopen
281 childTask
= launchTest(testProgPath
, "", false, true);
282 if ( ! monitor(childTask
, true, false) ) {
283 printf("[FAIL] dyld_process_info_notify connect/disconnect missed some notifications\n");
284 task_terminate(childTask
);
287 task_terminate(childTask
);
289 printf("[PASS] dyld_process_info_notify\n");