35d8f15aab4d89aa1adeac7d7aa6b18646b450fa
[apple/dyld.git] / testing / test-cases / dyld_process_info_notify.dtest / main.cpp
1
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
6
7 // RUN_TIMEOUT: 2400
8 // XFAIL: $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 #include "test_support.h"
24
25 extern char** environ;
26
27 void launchTest(bool launchSuspended, bool disconnectEarly)
28 {
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, ^{});
34 pid_t pid;
35
36 task_t task;
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;
46
47 __block int libFooLoadCount = 0;
48 __block int libFooUnloadCount = 0;
49 __block dyld_process_info_notify handle;
50
51 _process process;
52 process.set_executable(RUN_DIR "/target.exe");
53 const char* env[] = { "TEST_OUTPUT=None", NULL};
54 process.set_env(env);
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);
61 });
62 _process_config_set_exit_handler(process, ^(pid_t pid) {
63 LOG("DIED (pid: %d)", pid);
64 });
65 }
66 pid = process.launch(queue);
67
68 if (!launchSuspended && dispatch_semaphore_wait(taskStarted, dispatch_time(DISPATCH_TIME_NOW, 5LL * NSEC_PER_SEC)) != 0) {
69 FAIL("Child launch timeout");
70 }
71 #if 1
72 snprintf(&subTestNameBuffer[0], 256, "%s (arch: %d)", launchSuspended ? "launch suspended" : "launch suspend-in-main", currentArch);
73
74 if ( task_for_pid(mach_task_self(), pid, &task) != KERN_SUCCESS ) {
75 FAIL("task_for_pid()");
76 }
77
78 kern_return_t kr;
79 unsigned count = 0;
80 do {
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 )
86 sawlibSystem = true;
87 if ( strstr(path, "/libfoo.dylib") != NULL ) {
88 if ( !gotMainNotice ) {
89 gotFooNoticeBeforeMain = true;
90 }
91 if ( unload ) {
92 ++libFooUnloadCount;
93 } else {
94 ++libFooLoadCount;
95 }
96 if ( disconnectEarly ) {
97 LOG("EARLY DISCONNECT");
98 gotEarlyNotice = true;
99 dispatch_semaphore_signal(taskDone);
100 }
101 }
102 },
103 ^{
104 LOG("TERMINATED (pid: %d)", pid);
105 gotTerminationNotice = true;
106 dispatch_semaphore_signal(taskDone);
107 },
108 &kr);
109 ++count;
110 if ( handle == NULL )
111 LOG("_dyld_process_info_notify() returned NULL, result=%d, count=%d", kr, count);
112 } while ( (handle == NULL) && (count < 5) );
113
114 if ( handle == NULL ) {
115 FAIL("%s: did not not get handle", subTestName);
116 }
117
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;
127 });
128 kill(pid, SIGCONT);
129 LOG("Sent SIGCONT");
130 } else {
131 kill(pid, SIGUSR1);
132 LOG("Sent SIGUSR1");
133 }
134
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);
138 }
139
140 // dispatch_release(taskDone);
141 // dispatch_release(queue);
142 // _dyld_process_info_notify_release(handle);
143
144 // Do not run any tests associated with startup unless the kernel suspended us
145 // before main()
146 if (launchSuspended) {
147 if ( !sawMainExecutable ) {
148 FAIL("%s: did not get load notification of main executable", subTestName);
149 }
150
151 if ( !gotMainNotice ) {
152 FAIL("%s: did not get notification of main()", subTestName);
153 }
154
155 if ( gotMainNoticeBeforeAllInitialDylibs ) {
156 FAIL("%s: notification of main() arrived before all initial dylibs", subTestName);
157 }
158
159 if ( gotFooNoticeBeforeMain ) {
160 FAIL("%s: notification of main() arrived after libfoo load notice", subTestName);
161 }
162
163 if ( !sawlibSystem ) {
164 FAIL("%s: did not get load notification of libSystem", subTestName);
165 }
166 }
167
168 if ( disconnectEarly ) {
169 if ( libFooLoadCount != 1 ) {
170 FAIL("%s: got %d load notifications about libFoo instead of 1", subTestName, libFooLoadCount);
171 }
172 if ( libFooUnloadCount != 0 ) {
173 FAIL("%s: got %d unload notifications about libFoo instead of 1", subTestName, libFooUnloadCount);
174 }
175 }
176 else {
177 if ( libFooLoadCount != 3 ) {
178 FAIL("%s: got %d load notifications about libFoo instead of 3", subTestName, libFooLoadCount);
179 }
180 if ( libFooUnloadCount != 3 ) {
181 FAIL("%s: got %d unload notifications about libFoo instead of 3", subTestName, libFooUnloadCount);
182 }
183 }
184 #endif
185 }
186
187 #if 0
188 static void validateMaxNotifies(struct task_and_pid tp)
189 {
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) {
197 kern_return_t kr;
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);
203 },
204 ^{
205 LOG("target exited");
206 },
207 &kr);
208 if ( handles[i] == NULL ) {
209 if ( i == 8 ) {
210 // expected failure, because only 8 simultaneous connections allowed
211 // release one and try again
212 _dyld_process_info_notify_release(handles[4]);
213 handles[4] = NULL;
214 }
215 else {
216 LOG("_dyld_process_info_notify() returned NULL and kern_result=%d, on count=%d", kr, i);
217 killTest(tp);
218 exit(0);
219 }
220 }
221 }
222 // release all
223 for (int i=0; i < 10; ++i) {
224 if ( handles[i] != NULL ) {
225 _dyld_process_info_notify_release(handles[i]);
226 }
227 }
228 dispatch_release(serviceQueue);
229 }
230 #endif
231
232 static void testSelfAttach(void) {
233 LOG("7");
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);
237 LOG("7.5");
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;
242 }
243 },
244 ^{},
245 &kr);
246 LOG("8");
247 if ( handle == NULL ) {
248 LOG("_dyld_process_info_notify() returned NULL, result=%d", kr);
249 }
250 LOG("8.5");
251 void* h = dlopen(RUN_DIR "/libfoo.dylib", 0);
252 LOG("8.75");
253 dlclose(h);
254 if (!dylibLoadNotified) {
255 FAIL("testSelfAttach");
256 }
257 LOG("9");
258 }
259
260 int main(int argc, const char* argv[], const char* envp[], const char* apple[]) {
261
262 // test 1) launch test program suspended in same arch as this program
263 launchTest(true, false);
264
265 // test 2) launch test program in same arch as this program where it sleeps itself
266 launchTest(false, false);
267 // validateMaxNotifies(child);
268
269 // test 3) launch test program where we disconnect from it after first dlopen
270 launchTest(true, true);
271 // monitor("disconnect", child, true, false);
272
273 // test 4) attempt to monitor the monitoring process
274 // testSelfAttach();
275 PASS("Success");
276
277 }