]> git.saurik.com Git - apple/dyld.git/blob - testing/test-cases/dyld_process_info_notify.dtest/main.mm
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 }