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
7 // RUN: $SUDO ./dyld_process_info_notify.exe
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>
26 #include "test_support.h"
28 //FIXME: We need to add some concurrent access tests
29 //FIXME: Add cross architecture tests back now that arm64e macOS exists
31 extern char** environ;
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, ^{})) {}
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);
46 LOG("Signalling semaphore %s", _name);
51 dispatch_block_t _block;
54 void launchTest(bool launchSuspended, bool disconnectEarly)
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);
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");
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
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, ^{
77 dispatch_resume(usr1SignalSource);
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, ^{
85 dispatch_resume(usr2SignalSource);
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;
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
108 (void)waitpid(pid, &status, 0);
109 const char* exitType = "UNKNOWN";
110 if (WIFEXITED(status)) {
112 dispStatus = WEXITSTATUS(status);
114 if (WIFSIGNALED(status)) {
116 dispStatus = WTERMSIG(status);
118 LOG("DIED via %s (pid: %d, status: %d)", exitType, pid, dispStatus);
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()");
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 )
140 if ( strstr(path, "/libfoo.dylib") != NULL ) {
141 if ( !gotMainNotice ) {
142 gotFooNoticeBeforeMain = true;
148 if (disconnectEarly) {
149 _dyld_process_info_notify_release(handle);
155 LOG("TERMINATED (pid: %d)", pid);
156 childExitNotification.signal();
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");
165 if ( handle == NULL ) {
166 FAIL("Did not not get handle");
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;
184 if (!childReady.wait()) {
185 FAIL("Timed out waiting for child to signal it is ready");
189 if (!childDone.wait()) {
190 FAIL("Timed out waiting for child to finish dlopen()/dlclose() operations");
192 if (launchSuspended) {
193 if ( !sawMainExecutable ) {
194 FAIL("Did not get load notification of main executable");
196 if ( !gotMainNotice ) {
197 FAIL("Did not get notification of main()");
199 if ( gotMainNoticeBeforeAllInitialDylibs ) {
200 FAIL("Notification of main() arrived before all initial dylibs");
202 if ( gotFooNoticeBeforeMain ) {
203 FAIL("Notification of main() arrived after libfoo load notice");
205 if ( !sawlibSystem ) {
206 FAIL("Did not get load notification of libSystem");
211 if (!childExitNotification.wait()) {
212 FAIL("Timed out waiting for child exit notification via _dyld_process_info_notify");
214 if ( disconnectEarly ) {
215 if ( libFooLoadCount != 1 ) {
216 FAIL("Got %d load notifications about libFoo instead of 1", libFooLoadCount);
218 if ( libFooUnloadCount != 0 ) {
219 FAIL("Got %d unload notifications about libFoo instead of 1", libFooUnloadCount);
222 if ( libFooLoadCount != 3 ) {
223 FAIL("Got %d load notifications about libFoo instead of 3", libFooLoadCount);
225 if ( libFooUnloadCount != 3 ) {
226 FAIL("Got %d unload notifications about libFoo instead of 3", libFooUnloadCount);
229 if (!childExit.wait()) {
230 FAIL("Timed out waiting for child cleanup");
234 dispatch_source_cancel(usr1SignalSource);
235 dispatch_source_cancel(usr2SignalSource);
236 if (!disconnectEarly) {
237 _dyld_process_info_notify_release(handle);
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;
252 ^{ teardownSempahore.signal(); },
254 if ( handle == NULL ) {
255 LOG("_dyld_process_info_notify() returned NULL, result=%d", kr);
257 void* h = dlopen(RUN_DIR "/libfoo.dylib", 0);
259 if (!dylibLoadNotified) {
260 FAIL("testSelfAttach");
262 _dyld_process_info_notify_release(handle);
263 teardownSempahore.wait();
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");
271 dyld_all_image_infos* infos = (dyld_all_image_infos*)taskDyldInfo.all_image_info_addr;
273 // Find a slot for the right
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]);
282 int main(int argc, const char* argv[], const char* envp[], const char* apple[]) {
283 // test 1) attempt to monitor the monitoring process
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);