2 // BUILD: $CC target.c -o $BUILD_DIR/target.exe -DRUN_DIR="$RUN_DIR"
3 // BUILD: $CC foo.c -o $BUILD_DIR/libfoo.dylib -dynamiclib
4 // BUILD: $CXX main.cpp -o $BUILD_DIR/dyld_process_info_notify.exe -DRUN_DIR="$RUN_DIR"
5 // BUILD: $TASK_FOR_PID_ENABLE $BUILD_DIR/dyld_process_info_notify.exe
8 // XFAIL: $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>
23 #include "test_support.h"
25 extern char** environ
;
27 void launchTest(bool launchSuspended
, bool disconnectEarly
)
29 LOG("launchTest (%s)", launchSuspended
? "suspended" : "unsuspened");
30 dispatch_queue_t queue
= dispatch_queue_create("com.apple.dyld.test.dyld_process_info", NULL
);
31 // We do this instead of using a dispatch_semaphore to prevent priority inversions
32 dispatch_block_t taskDone
= dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS
, ^{});
33 dispatch_block_t taskStarted
= dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS
, ^{});
37 char subTestNameBuffer
[256];
38 char *subTestName
= &subTestNameBuffer
[0];
39 __block
bool sawMainExecutable
= false;
40 __block
bool sawlibSystem
= false;
41 __block
bool gotTerminationNotice
= false;
42 __block
bool gotEarlyNotice
= false;
43 __block
bool gotMainNotice
= false;
44 __block
bool gotMainNoticeBeforeAllInitialDylibs
= false;
45 __block
bool gotFooNoticeBeforeMain
= false;
47 __block
int libFooLoadCount
= 0;
48 __block
int libFooUnloadCount
= 0;
49 __block dyld_process_info_notify handle
;
52 process
.set_executable(RUN_DIR
"/target.exe");
53 const char* env
[] = { "TEST_OUTPUT=None", NULL
};
55 process
.set_launch_suspended(launchSuspended
);
56 if (!launchSuspended
) {
57 const char* args
[] = {"suspend-in-main", NULL
};
58 _process_config_set_args(process
, args
);
59 _process_config_set_stderr_handler(process
, ^(int fd
) {
60 dispatch_semaphore_signal(taskStarted
);
62 _process_config_set_exit_handler(process
, ^(pid_t pid
) {
63 LOG("DIED (pid: %d)", pid
);
66 pid
= process
.launch(queue
);
68 if (!launchSuspended
&& dispatch_semaphore_wait(taskStarted
, dispatch_time(DISPATCH_TIME_NOW
, 5LL * NSEC_PER_SEC
)) != 0) {
69 FAIL("Child launch timeout");
72 snprintf(&subTestNameBuffer
[0], 256, "%s (arch: %d)", launchSuspended
? "launch suspended" : "launch suspend-in-main", currentArch
);
74 if ( task_for_pid(mach_task_self(), pid
, &task
) != KERN_SUCCESS
) {
75 FAIL("task_for_pid()");
81 handle
= _dyld_process_info_notify(task
, queue
,
82 ^(bool unload
, uint64_t timestamp
, uint64_t machHeader
, const uuid_t uuid
, const char* path
) {
83 if ( strstr(path
, "/target.exe") != NULL
)
84 sawMainExecutable
= true;
85 if ( strstr(path
, "/libSystem") != NULL
)
87 if ( strstr(path
, "/libfoo.dylib") != NULL
) {
88 if ( !gotMainNotice
) {
89 gotFooNoticeBeforeMain
= true;
96 if ( disconnectEarly
) {
97 LOG("EARLY DISCONNECT");
98 gotEarlyNotice
= true;
99 dispatch_semaphore_signal(taskDone
);
104 LOG("TERMINATED (pid: %d)", pid
);
105 gotTerminationNotice
= true;
106 dispatch_semaphore_signal(taskDone
);
110 if ( handle
== NULL
)
111 LOG("_dyld_process_info_notify() returned NULL, result=%d, count=%d", kr
, count
);
112 } while ( (handle
== NULL
) && (count
< 5) );
114 if ( handle
== NULL
) {
115 FAIL("%s: did not not get handle", subTestName
);
118 if (launchSuspended
) {
119 // If the process starts suspended register for main(),
120 // otherwise skip since this test is a race between
121 // process setup and notification registration
122 _dyld_process_info_notify_main(handle
, ^{
123 LOG("target entering main()");
124 gotMainNotice
= true;
125 if ( !sawMainExecutable
|| !sawlibSystem
)
126 gotMainNoticeBeforeAllInitialDylibs
= true;
135 // block waiting for notification that target has exited
136 if (dispatch_semaphore_wait(taskDone
, dispatch_time(DISPATCH_TIME_NOW
, 10LL * NSEC_PER_SEC
)) != 0) {
137 FAIL("%s: did not get exit signal", subTestName
);
140 // dispatch_release(taskDone);
141 // dispatch_release(queue);
142 // _dyld_process_info_notify_release(handle);
144 // Do not run any tests associated with startup unless the kernel suspended us
146 if (launchSuspended
) {
147 if ( !sawMainExecutable
) {
148 FAIL("%s: did not get load notification of main executable", subTestName
);
151 if ( !gotMainNotice
) {
152 FAIL("%s: did not get notification of main()", subTestName
);
155 if ( gotMainNoticeBeforeAllInitialDylibs
) {
156 FAIL("%s: notification of main() arrived before all initial dylibs", subTestName
);
159 if ( gotFooNoticeBeforeMain
) {
160 FAIL("%s: notification of main() arrived after libfoo load notice", subTestName
);
163 if ( !sawlibSystem
) {
164 FAIL("%s: did not get load notification of libSystem", subTestName
);
168 if ( disconnectEarly
) {
169 if ( libFooLoadCount
!= 1 ) {
170 FAIL("%s: got %d load notifications about libFoo instead of 1", subTestName
, libFooLoadCount
);
172 if ( libFooUnloadCount
!= 0 ) {
173 FAIL("%s: got %d unload notifications about libFoo instead of 1", subTestName
, libFooUnloadCount
);
177 if ( libFooLoadCount
!= 3 ) {
178 FAIL("%s: got %d load notifications about libFoo instead of 3", subTestName
, libFooLoadCount
);
180 if ( libFooUnloadCount
!= 3 ) {
181 FAIL("%s: got %d unload notifications about libFoo instead of 3", subTestName
, libFooUnloadCount
);
188 static void validateMaxNotifies(struct task_and_pid tp
)
190 dispatch_queue_t serviceQueue
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH
, 0);
191 dyld_process_info_notify handles
[10];
192 // This loop goes through 10 iterations
193 // i = 0..7 Should succeed
194 // i = 8 Should fail, but trigger a release that frees up a slot
195 // i = 9 Should succeed
196 for (int i
=0; i
< 10; ++i
) {
198 handles
[i
] = _dyld_process_info_notify(tp
.task
, serviceQueue
,
199 ^(bool unload
, uint64_t timestamp
, uint64_t machHeader
, const uuid_t uuid
, const char* path
) {
200 LOG("unload=%d, 0x%012llX <%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X> %s",
201 unload
, machHeader
, uuid
[0], uuid
[1], uuid
[2], uuid
[3], uuid
[4], uuid
[5], uuid
[6], uuid
[7],
202 uuid
[8], uuid
[9], uuid
[10], uuid
[11], uuid
[12], uuid
[13], uuid
[14], uuid
[15], path
);
205 LOG("target exited");
208 if ( handles
[i
] == NULL
) {
210 // expected failure, because only 8 simultaneous connections allowed
211 // release one and try again
212 _dyld_process_info_notify_release(handles
[4]);
216 LOG("_dyld_process_info_notify() returned NULL and kern_result=%d, on count=%d", kr
, i
);
223 for (int i
=0; i
< 10; ++i
) {
224 if ( handles
[i
] != NULL
) {
225 _dyld_process_info_notify_release(handles
[i
]);
228 dispatch_release(serviceQueue
);
232 static void testSelfAttach(void) {
234 __block
bool dylibLoadNotified
= false;
235 kern_return_t kr
= KERN_SUCCESS
;
236 dispatch_queue_t queue
= dispatch_queue_create("com.apple.dyld.test.dyld_process_info.self-attach", NULL
);
238 dyld_process_info_notify handle
= _dyld_process_info_notify(mach_task_self(), queue
,
239 ^(bool unload
, uint64_t timestamp
, uint64_t machHeader
, const uuid_t uuid
, const char* path
) {
240 if ( strstr(path
, "/libfoo.dylib") != NULL
) {
241 dylibLoadNotified
= true;
247 if ( handle
== NULL
) {
248 LOG("_dyld_process_info_notify() returned NULL, result=%d", kr
);
251 void* h
= dlopen(RUN_DIR
"/libfoo.dylib", 0);
254 if (!dylibLoadNotified
) {
255 FAIL("testSelfAttach");
260 int main(int argc
, const char* argv
[], const char* envp
[], const char* apple
[]) {
262 // test 1) launch test program suspended in same arch as this program
263 launchTest(true, false);
265 // test 2) launch test program in same arch as this program where it sleeps itself
266 launchTest(false, false);
267 // validateMaxNotifies(child);
269 // test 3) launch test program where we disconnect from it after first dlopen
270 launchTest(true, true);
271 // monitor("disconnect", child, true, false);
273 // test 4) attempt to monitor the monitoring process