dyld-851.27.tar.gz
[apple/dyld.git] / testing / test-cases / dyld_process_info_notify.dtest / main.mm
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.mm     -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:  $SUDO ./dyld_process_info_notify.exe
8
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <dlfcn.h>
12 #include <unistd.h>
13 #include <signal.h>
14 #include <spawn.h>
15 #include <errno.h>
16 #include <libgen.h>
17 #include <sys/proc.h>
18 #include <mach/mach.h>
19 #include <sys/param.h>
20 #include <mach/machine.h>
21 #include <mach-o/dyld_images.h>
22 #include <mach-o/dyld_process_info.h>
23 #include <dispatch/dispatch.h>
24 #include <Availability.h>
25
26 #include "test_support.h"
27
28 //FIXME: We need to add some concurrent access tests
29 //FIXME: Add cross architecture tests back now that arm64e macOS exists
30
31 extern char** environ;
32
33 // This is a one shot semaphore implementation that is QoS aware with integreated logging
34 struct OneShotSemaphore {
35     OneShotSemaphore(const char* N) :_name(strdup(N)), _block(dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, ^{})) {}
36     bool wait() {
37         LOG("Waiting for semaphore %s", _name);
38         dispatch_time_t tenSecondFromNow = dispatch_time(DISPATCH_WALLTIME_NOW, 10 * NSEC_PER_SEC);
39         if (dispatch_block_wait(_block, tenSecondFromNow) != 0) {
40             LOG("Timeout for semaphore %s", _name);
41             return false;
42         }
43         return true;
44     }
45     void signal() {
46         LOG("Signalling semaphore %s", _name);
47         _block();
48     }
49 private:
50     const char*         _name;
51     dispatch_block_t    _block;
52 };
53
54 void launchTest(bool launchSuspended, bool disconnectEarly)
55 {
56
57     LOG("launchTest (%s)", launchSuspended ? "suspended" : "unsuspened");
58     LOG("launchTest (%s)", disconnectEarly ? "disconnect early" : "normal disconnnect");
59     dispatch_queue_t queue = dispatch_queue_create("com.apple.dyld.test.dyld_process_info", NULL);
60     dispatch_queue_t signalQueue = dispatch_queue_create("com.apple.dyld.test.dyld_process_info.signals", NULL);
61
62     // We use these blocks as semaphores. We do it this way so have ownership for QOS and so we get logging
63     __block OneShotSemaphore childReady("childReady");
64     __block OneShotSemaphore childExit("childExit");
65     __block OneShotSemaphore childDone("childDone");
66     __block OneShotSemaphore childExitNotification("childExitNotification");
67
68     // We control our interactions with the sub ordinate process via signals, but if we send signals before its signal handlers
69     // are installed it will terminate. We wait for it to SIGUSR1 us to indicate it is ready, so we need to setup a signal handler for
70     // that.
71     signal(SIGUSR1, SIG_IGN);
72     dispatch_source_t usr1SignalSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, signalQueue);
73     dispatch_source_set_event_handler(usr1SignalSource, ^{
74         LOG("Got SIGUSR1");
75         childReady.signal();
76     });
77     dispatch_resume(usr1SignalSource);
78
79     signal(SIGUSR2, SIG_IGN);
80     dispatch_source_t usr2SignalSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR2, 0, signalQueue);
81     dispatch_source_set_event_handler(usr2SignalSource, ^{
82         LOG("Got SIGUSR2");
83         childDone.signal();
84     });
85     dispatch_resume(usr2SignalSource);
86
87     pid_t pid;
88     task_t task;
89     __block bool sawMainExecutable = false;
90     __block bool sawlibSystem = false;
91     __block bool gotMainNotice = false;
92     __block bool gotMainNoticeBeforeAllInitialDylibs = false;
93     __block bool gotFooNoticeBeforeMain = false;
94     __block int libFooLoadCount = 0;
95     __block int libFooUnloadCount = 0;
96     __block dyld_process_info_notify handle;
97
98     _process process;
99     process.set_executable_path(RUN_DIR "/target.exe");
100     const char* env[] = { "TEST_OUTPUT=None", NULL};
101     process.set_env(env);
102     process.set_launch_suspended(launchSuspended);
103     process.set_exit_handler(^(pid_t pid) {
104         // This is almost all logging code, the only functional element of it
105         // is calling the childExit() semaphore
106         int status = 0;
107         int dispStatus = 0;
108         (void)waitpid(pid, &status, 0);
109         const char* exitType = "UNKNOWN";
110         if (WIFEXITED(status)) {
111             exitType = "exit()";
112             dispStatus = WEXITSTATUS(status);
113         }
114         if (WIFSIGNALED(status)) {
115             exitType = "signal";
116             dispStatus = WTERMSIG(status);
117         }
118         LOG("DIED via %s (pid: %d, status: %d)", exitType, pid, dispStatus);
119         childExit.signal();
120     });
121
122     // Launch process
123     pid = process.launch();
124     LOG("launchTest pid (%u)", pid);
125     if ( task_read_for_pid(mach_task_self(), pid, &task) != KERN_SUCCESS ) {
126         FAIL("task_read_for_pid()");
127     }
128
129     // Attach notifier
130     kern_return_t kr;
131     unsigned count = 0;
132     do {
133         handle = _dyld_process_info_notify( task, queue,
134                                             ^(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path) {
135                                                 LOG("Handler called");
136                                                 if ( strstr(path, "/target.exe") != NULL )
137                                                     sawMainExecutable = true;
138                                                 if ( strstr(path, "/libSystem") != NULL )
139                                                     sawlibSystem = true;
140                                                 if ( strstr(path, "/libfoo.dylib") != NULL ) {
141                                                     if ( !gotMainNotice ) {
142                                                         gotFooNoticeBeforeMain = true;
143                                                     }
144                                                     if ( unload ) {
145                                                         ++libFooUnloadCount;
146                                                     } else {
147                                                         ++libFooLoadCount;
148                                                         if (disconnectEarly) {
149                                                             _dyld_process_info_notify_release(handle);
150                                                         }
151                                                     }
152                                                 }
153                                             },
154                                             ^{
155                                                 LOG("TERMINATED (pid: %d)", pid);
156                                                 childExitNotification.signal();
157                                             },
158                                             &kr);
159         ++count;
160         if ( handle == NULL )
161             LOG("_dyld_process_info_notify() returned NULL, result=%d, count=%d", kr, count);
162     } while ( (handle == NULL) && (count < 5) );
163     LOG("launchTest handler registered");
164
165     if ( handle == NULL ) {
166         FAIL("Did not not get handle");
167     }
168
169     // if suspended attach main notifier and unsuspend
170     if (launchSuspended) {
171         // If the process starts suspended register for main(),
172         // otherwise skip since this test is a race between
173         // process setup and notification registration
174         _dyld_process_info_notify_main(handle, ^{
175                                                 LOG("target entering main()");
176                                                 gotMainNotice = true;
177                                                 if ( !sawMainExecutable || !sawlibSystem )
178                                                     gotMainNoticeBeforeAllInitialDylibs = true;
179                                                 });
180         kill(pid, SIGCONT);
181         LOG("Sent SIGCONT");
182     }
183
184     if (!childReady.wait()) {
185         FAIL("Timed out waiting for child to signal it is ready");
186     }
187     kill(pid, SIGUSR1);
188     LOG("Sent SIGUSR1");
189     if (!childDone.wait()) {
190         FAIL("Timed out waiting for child to finish dlopen()/dlclose() operations");
191     }
192     if (launchSuspended) {
193         if ( !sawMainExecutable ) {
194             FAIL("Did not get load notification of main executable");
195         }
196         if ( !gotMainNotice ) {
197             FAIL("Did not get notification of main()");
198         }
199         if ( gotMainNoticeBeforeAllInitialDylibs ) {
200             FAIL("Notification of main() arrived before all initial dylibs");
201         }
202         if ( gotFooNoticeBeforeMain ) {
203             FAIL("Notification of main() arrived after libfoo load notice");
204         }
205         if ( !sawlibSystem ) {
206             FAIL("Did not get load notification of libSystem");
207         }
208     }
209     kill(pid, SIGTERM);
210     LOG("Sent SIGTERM");
211     if (!childExitNotification.wait()) {
212         FAIL("Timed out waiting for child exit notification via _dyld_process_info_notify");
213     }
214     if ( disconnectEarly ) {
215         if ( libFooLoadCount != 1 ) {
216             FAIL("Got %d load notifications about libFoo instead of 1", libFooLoadCount);
217         }
218         if ( libFooUnloadCount != 0 ) {
219             FAIL("Got %d unload notifications about libFoo instead of 1", libFooUnloadCount);
220         }
221     } else {
222         if ( libFooLoadCount != 3 ) {
223             FAIL("Got %d load notifications about libFoo instead of 3", libFooLoadCount);
224         }
225         if ( libFooUnloadCount != 3 ) {
226             FAIL("Got %d unload notifications about libFoo instead of 3", libFooUnloadCount);
227         }
228     }
229     if (!childExit.wait()) {
230         FAIL("Timed out waiting for child cleanup");
231     }
232
233     // Tear down
234     dispatch_source_cancel(usr1SignalSource);
235     dispatch_source_cancel(usr2SignalSource);
236     if (!disconnectEarly) {
237         _dyld_process_info_notify_release(handle);
238     }
239 }
240
241 static void testSelfAttach(void) {
242     __block OneShotSemaphore teardownSempahore("self test teardownSempahore");
243     __block bool dylibLoadNotified = false;
244     kern_return_t kr = KERN_SUCCESS;
245     dispatch_queue_t queue = dispatch_queue_create("com.apple.dyld.test.dyld_process_info.self-attach", NULL);
246     dyld_process_info_notify handle = _dyld_process_info_notify(mach_task_self(), queue,
247                                        ^(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path) {
248                                            if ( strstr(path, "/libfoo.dylib") != NULL ) {
249                                                dylibLoadNotified = true;
250                                            }
251                                        },
252                                                                 ^{ teardownSempahore.signal(); },
253                                        &kr);
254     if ( handle == NULL ) {
255         LOG("_dyld_process_info_notify() returned NULL, result=%d", kr);
256     }
257     void* h = dlopen(RUN_DIR "/libfoo.dylib", 0);
258     dlclose(h);
259     if (!dylibLoadNotified) {
260         FAIL("testSelfAttach");
261     }
262     _dyld_process_info_notify_release(handle);
263     teardownSempahore.wait();
264
265     // Get the all image info
266     task_dyld_info_data_t taskDyldInfo;
267     mach_msg_type_number_t taskDyldInfoCount = TASK_DYLD_INFO_COUNT;
268     if (task_info(mach_task_self(), TASK_DYLD_INFO, (task_info_t)&taskDyldInfo, &taskDyldInfoCount) != KERN_SUCCESS) {
269         FAIL("Could not find all image info");
270     }
271     dyld_all_image_infos* infos = (dyld_all_image_infos*)taskDyldInfo.all_image_info_addr;
272
273     // Find a slot for the right
274     uint8_t notifySlot;
275     for (uint8_t notifySlot = 0; notifySlot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++notifySlot) {
276         if (infos->notifyPorts[notifySlot] != 0) {
277             FAIL("Port array entry %u not cleaned up, expected 0, got %u", notifySlot, infos->notifyPorts[notifySlot]);
278         }
279     }
280 }
281
282 int main(int argc, const char* argv[], const char* envp[], const char* apple[]) {
283     // test 1) attempt to monitor the monitoring process
284     testSelfAttach();
285     // test 2) launch test program suspended and wait for it to run to completion
286     launchTest(true, false);
287     // test 3) launch test program in unsuspended and wait for it to run to completion
288     launchTest(false, false);
289     // test 4) launch test program suspended and disconnect from it after the first dlopen() in target.exe
290     launchTest(true, true);
291     // test 5) launch test program unsuspended and disconnect from it after the first dlopen() in target.exe
292     launchTest(false, true);
293
294     PASS("Success");
295 }