--- /dev/null
+
+// BUILD: $CC target.c -o $BUILD_DIR/target.exe -DRUN_DIR="$RUN_DIR"
+// BUILD: $CC foo.c -o $BUILD_DIR/libfoo.dylib -dynamiclib
+// BUILD: $CXX main.cpp -o $BUILD_DIR/dyld_process_info_notify.exe -DRUN_DIR="$RUN_DIR"
+// BUILD: $TASK_FOR_PID_ENABLE $BUILD_DIR/dyld_process_info_notify.exe
+
+// RUN_TIMEOUT: 2400
+// XFAIL: $SUDO ./dyld_process_info_notify.exe $RUN_DIR/target.exe
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <dlfcn.h>
+#include <unistd.h>
+#include <signal.h>
+#include <spawn.h>
+#include <errno.h>
+#include <mach/mach.h>
+#include <mach/machine.h>
+#include <mach-o/dyld_process_info.h>
+#include <dispatch/dispatch.h>
+#include <Availability.h>
+
+#include "test_support.h"
+
+extern char** environ;
+
+void launchTest(bool launchSuspended, bool disconnectEarly)
+{
+ LOG("launchTest (%s)", launchSuspended ? "suspended" : "unsuspened");
+ dispatch_queue_t queue = dispatch_queue_create("com.apple.dyld.test.dyld_process_info", NULL);
+ // We do this instead of using a dispatch_semaphore to prevent priority inversions
+ dispatch_block_t taskDone = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, ^{});
+ dispatch_block_t taskStarted = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, ^{});
+ pid_t pid;
+
+ task_t task;
+ char subTestNameBuffer[256];
+ char *subTestName = &subTestNameBuffer[0];
+ __block bool sawMainExecutable = false;
+ __block bool sawlibSystem = false;
+ __block bool gotTerminationNotice = false;
+ __block bool gotEarlyNotice = false;
+ __block bool gotMainNotice = false;
+ __block bool gotMainNoticeBeforeAllInitialDylibs = false;
+ __block bool gotFooNoticeBeforeMain = false;
+
+ __block int libFooLoadCount = 0;
+ __block int libFooUnloadCount = 0;
+ __block dyld_process_info_notify handle;
+
+ _process process;
+ process.set_executable(RUN_DIR "/target.exe");
+ const char* env[] = { "TEST_OUTPUT=None", NULL};
+ process.set_env(env);
+ process.set_launch_suspended(launchSuspended);
+ if (!launchSuspended) {
+ const char* args[] = {"suspend-in-main", NULL};
+ _process_config_set_args(process, args);
+ _process_config_set_stderr_handler(process, ^(int fd) {
+ dispatch_semaphore_signal(taskStarted);
+ });
+ _process_config_set_exit_handler(process, ^(pid_t pid) {
+ LOG("DIED (pid: %d)", pid);
+ });
+ }
+ pid = process.launch(queue);
+
+ if (!launchSuspended && dispatch_semaphore_wait(taskStarted, dispatch_time(DISPATCH_TIME_NOW, 5LL * NSEC_PER_SEC)) != 0) {
+ FAIL("Child launch timeout");
+ }
+#if 1
+ snprintf(&subTestNameBuffer[0], 256, "%s (arch: %d)", launchSuspended ? "launch suspended" : "launch suspend-in-main", currentArch);
+
+ if ( task_for_pid(mach_task_self(), pid, &task) != KERN_SUCCESS ) {
+ FAIL("task_for_pid()");
+ }
+
+ kern_return_t kr;
+ unsigned count = 0;
+ do {
+ handle = _dyld_process_info_notify(task, queue,
+ ^(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path) {
+ if ( strstr(path, "/target.exe") != NULL )
+ sawMainExecutable = true;
+ if ( strstr(path, "/libSystem") != NULL )
+ sawlibSystem = true;
+ if ( strstr(path, "/libfoo.dylib") != NULL ) {
+ if ( !gotMainNotice ) {
+ gotFooNoticeBeforeMain = true;
+ }
+ if ( unload ) {
+ ++libFooUnloadCount;
+ } else {
+ ++libFooLoadCount;
+ }
+ if ( disconnectEarly ) {
+ LOG("EARLY DISCONNECT");
+ gotEarlyNotice = true;
+ dispatch_semaphore_signal(taskDone);
+ }
+ }
+ },
+ ^{
+ LOG("TERMINATED (pid: %d)", pid);
+ gotTerminationNotice = true;
+ dispatch_semaphore_signal(taskDone);
+ },
+ &kr);
+ ++count;
+ if ( handle == NULL )
+ LOG("_dyld_process_info_notify() returned NULL, result=%d, count=%d", kr, count);
+ } while ( (handle == NULL) && (count < 5) );
+
+ if ( handle == NULL ) {
+ FAIL("%s: did not not get handle", subTestName);
+ }
+
+ if (launchSuspended) {
+ // If the process starts suspended register for main(),
+ // otherwise skip since this test is a race between
+ // process setup and notification registration
+ _dyld_process_info_notify_main(handle, ^{
+ LOG("target entering main()");
+ gotMainNotice = true;
+ if ( !sawMainExecutable || !sawlibSystem )
+ gotMainNoticeBeforeAllInitialDylibs = true;
+ });
+ kill(pid, SIGCONT);
+ LOG("Sent SIGCONT");
+ } else {
+ kill(pid, SIGUSR1);
+ LOG("Sent SIGUSR1");
+ }
+
+ // block waiting for notification that target has exited
+ if (dispatch_semaphore_wait(taskDone, dispatch_time(DISPATCH_TIME_NOW, 10LL * NSEC_PER_SEC)) != 0) {
+ FAIL("%s: did not get exit signal", subTestName);
+ }
+
+// dispatch_release(taskDone);
+// dispatch_release(queue);
+// _dyld_process_info_notify_release(handle);
+
+ // Do not run any tests associated with startup unless the kernel suspended us
+ // before main()
+ if (launchSuspended) {
+ if ( !sawMainExecutable ) {
+ FAIL("%s: did not get load notification of main executable", subTestName);
+ }
+
+ if ( !gotMainNotice ) {
+ FAIL("%s: did not get notification of main()", subTestName);
+ }
+
+ if ( gotMainNoticeBeforeAllInitialDylibs ) {
+ FAIL("%s: notification of main() arrived before all initial dylibs", subTestName);
+ }
+
+ if ( gotFooNoticeBeforeMain ) {
+ FAIL("%s: notification of main() arrived after libfoo load notice", subTestName);
+ }
+
+ if ( !sawlibSystem ) {
+ FAIL("%s: did not get load notification of libSystem", subTestName);
+ }
+ }
+
+ if ( disconnectEarly ) {
+ if ( libFooLoadCount != 1 ) {
+ FAIL("%s: got %d load notifications about libFoo instead of 1", subTestName, libFooLoadCount);
+ }
+ if ( libFooUnloadCount != 0 ) {
+ FAIL("%s: got %d unload notifications about libFoo instead of 1", subTestName, libFooUnloadCount);
+ }
+ }
+ else {
+ if ( libFooLoadCount != 3 ) {
+ FAIL("%s: got %d load notifications about libFoo instead of 3", subTestName, libFooLoadCount);
+ }
+ if ( libFooUnloadCount != 3 ) {
+ FAIL("%s: got %d unload notifications about libFoo instead of 3", subTestName, libFooUnloadCount);
+ }
+ }
+#endif
+}
+
+#if 0
+static void validateMaxNotifies(struct task_and_pid tp)
+{
+ dispatch_queue_t serviceQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
+ dyld_process_info_notify handles[10];
+ // This loop goes through 10 iterations
+ // i = 0..7 Should succeed
+ // i = 8 Should fail, but trigger a release that frees up a slot
+ // i = 9 Should succeed
+ for (int i=0; i < 10; ++i) {
+ kern_return_t kr;
+ handles[i] = _dyld_process_info_notify(tp.task, serviceQueue,
+ ^(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path) {
+ LOG("unload=%d, 0x%012llX <%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X> %s",
+ unload, machHeader, uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7],
+ uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15], path);
+ },
+ ^{
+ LOG("target exited");
+ },
+ &kr);
+ if ( handles[i] == NULL ) {
+ if ( i == 8 ) {
+ // expected failure, because only 8 simultaneous connections allowed
+ // release one and try again
+ _dyld_process_info_notify_release(handles[4]);
+ handles[4] = NULL;
+ }
+ else {
+ LOG("_dyld_process_info_notify() returned NULL and kern_result=%d, on count=%d", kr, i);
+ killTest(tp);
+ exit(0);
+ }
+ }
+ }
+ // release all
+ for (int i=0; i < 10; ++i) {
+ if ( handles[i] != NULL ) {
+ _dyld_process_info_notify_release(handles[i]);
+ }
+ }
+ dispatch_release(serviceQueue);
+}
+#endif
+
+static void testSelfAttach(void) {
+ LOG("7");
+ __block bool dylibLoadNotified = false;
+ kern_return_t kr = KERN_SUCCESS;
+ dispatch_queue_t queue = dispatch_queue_create("com.apple.dyld.test.dyld_process_info.self-attach", NULL);
+ LOG("7.5");
+ dyld_process_info_notify handle = _dyld_process_info_notify(mach_task_self(), queue,
+ ^(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path) {
+ if ( strstr(path, "/libfoo.dylib") != NULL ) {
+ dylibLoadNotified = true;
+ }
+ },
+ ^{},
+ &kr);
+ LOG("8");
+ if ( handle == NULL ) {
+ LOG("_dyld_process_info_notify() returned NULL, result=%d", kr);
+ }
+ LOG("8.5");
+ void* h = dlopen(RUN_DIR "/libfoo.dylib", 0);
+ LOG("8.75");
+ dlclose(h);
+ if (!dylibLoadNotified) {
+ FAIL("testSelfAttach");
+ }
+ LOG("9");
+}
+
+int main(int argc, const char* argv[], const char* envp[], const char* apple[]) {
+
+ // test 1) launch test program suspended in same arch as this program
+ launchTest(true, false);
+
+ // test 2) launch test program in same arch as this program where it sleeps itself
+ launchTest(false, false);
+// validateMaxNotifies(child);
+
+ // test 3) launch test program where we disconnect from it after first dlopen
+ launchTest(true, true);
+// monitor("disconnect", child, true, false);
+
+ // test 4) attempt to monitor the monitoring process
+// testSelfAttach();
+ PASS("Success");
+
+}