Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 | // BOOT_ARGS: dyld_flags=0x00010000 // BUILD: $CC target.c -o $BUILD_DIR/target.exe -framework CoreFoundation -DRUN_DIR="$RUN_DIR" // BUILD: $CC foo.c -o $BUILD_DIR/libfoo.bundle -bundle // BUILD: $CXX main.cpp -std=c++17 -o $BUILD_DIR/dyld_process_introspection.exe -DRUN_DIR="$RUN_DIR" // BUILD: $TASK_FOR_PID_ENABLE $BUILD_DIR/dyld_process_introspection.exe // RUN: $SUDO ./dyld_process_introspection.exe #include <Block.h> #include <cstdio> #include <cstdlib> #include <utility> #include <dlfcn.h> #include <unistd.h> #include <signal.h> #include <spawn.h> #include <errno.h> #include <sys/uio.h> #include <sys/proc.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/mman.h> #include <mach/mach.h> #include <sys/socket.h> #include <mach/machine.h> #include <mach-o/dyld_introspection.h> #include <Availability.h> #include "test_support.h" static void inspectProcess(task_t task, bool launchedSuspended, bool expectCF) { kern_return_t result; dyld_process_t process = dyld_process_create_for_task(task, &result); if (result != KERN_SUCCESS) { FAIL("dyld_process_create_for_task() should succeed, get return code %d", result); } if (process == NULL) { FAIL("dyld_process_create_for_task(task, 0) alwats return a value"); } dyld_process_snapshot_t snapshot = dyld_process_snapshot_create_for_process(process, &result); dyld_process_dispose(process); if (result != KERN_SUCCESS) { FAIL("dyld_process_snapshot_create_for_process() should succeed, got return code %d", result); } if (process == NULL) { FAIL("dyld_process_snapshot_create_for_process(process, 0) alwats return a value"); } __block bool foundDyld = false; __block bool foundMain = false; __block bool foundCF = false; dyld_process_snapshot_for_each_image(snapshot, ^(dyld_image_t image) { const char* path = dyld_image_get_file_path(image); const char* installname = dyld_image_get_installname(image); if ( installname && (strcmp(installname, "/usr/lib/dyld") == 0)) { foundDyld = true; } if ( path && strstr(path, "/target.exe") != NULL ) { foundMain = true; } if ( path && (strstr(path, "/dyld_process_introspection.exe") != NULL )) { foundMain = true; } if ( installname && strstr(installname, "/CoreFoundation.framework/") != NULL ) { foundCF = true; } __block bool isFirstSegment = true; __block uint64_t textAddr = 0; __block uint64_t textSize = 0; dyld_image_for_each_segment_info(image, ^(const char *segmentName, uint64_t vmAddr, uint64_t vmSize, int perm) { if ( isFirstSegment && vmAddr == 0 ) { char* displayPath = NULL; if ( path ) displayPath = (char*)path; if ( installname ) displayPath = (char*)installname; FAIL("dyld_image_for_each_segment_info returned incorrect vmAddr: %s(%s): (0x%llx - 0x%llx)", displayPath, segmentName, vmAddr, vmAddr+vmSize); } isFirstSegment = false; if (strcmp(segmentName, "__TEXT") == 0) { textAddr = vmAddr; textSize = vmSize; char* displayPath = NULL; if ( path ) displayPath = (char*)path; if ( installname ) displayPath = (char*)installname; // fprintf(stderr,"%s 0x%llx 0x%llx\n", displayPath, vmAddr, vmAddr); if (perm != (PROT_EXEC | PROT_READ) ) { FAIL("dyld_image_for_each_segment_info returned incorrect permissions for __TEXT segment: 0x%lx)", perm); } } }); __block bool didRunTEXTCallback = false; dyld_image_content_for_segment(image, "__TEXT", ^(const void *content, uint64_t vmAddr, uint64_t vmSize) { didRunTEXTCallback = true; auto mh = (const mach_header*)content; if (mh->magic != MH_MAGIC_64 && mh->magic != MH_MAGIC) { FAIL("dyld_image_content_for_segment returned incorrect magic: 0x%lx", mh->magic); } if (vmAddr != textAddr) { FAIL("dyld_image_content_for_segment returned incorrect vmAddr: 0x%llx, expected 0x%llx", vmAddr, textAddr); } if (vmAddr != textAddr) { FAIL("dyld_image_content_for_segment returned incorrect vmSize: 0x%llx, expected 0x%llx", vmSize, textSize); } }); if( !didRunTEXTCallback ) { FAIL("callback did not run for __TEXT segment"); } }); if (!foundDyld) { FAIL("dyld should always be in the image list"); } if (!foundMain) { FAIL("The main executable should always be in the image list"); } if (expectCF && !foundCF) { FAIL("CF should be in the image list"); } dyld_process_snapshot_dispose(snapshot); } void waitForTargetCheckin(pid_t pid) { dispatch_queue_t queue = dispatch_queue_create("com.apple.test.dyld_process_introspection", NULL); // We do this instead of using a dispatch_semaphore to prevent priority inversions dispatch_block_t oneShotSemaphore = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, ^{}); dispatch_source_t signalSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, queue); dispatch_source_set_event_handler(signalSource, ^{ LOG("Received signal"); oneShotSemaphore(); dispatch_source_cancel(signalSource); }); dispatch_resume(signalSource); kill(pid, SIGCONT); dispatch_block_wait(oneShotSemaphore, DISPATCH_TIME_FOREVER); dispatch_release(queue); } static std::pair<_process,task_t> launchTarget(bool launchSuspended) { LOG("launchTarget %s", launchSuspended ? "suspended" : "unsuspended"); const char * program = RUN_DIR "/target.exe"; _process process; process.set_executable_path(RUN_DIR "/target.exe"); process.set_launch_suspended(true); const char* env[] = { "TEST_OUTPUT=None", NULL}; process.set_env(env); pid_t pid = process.launch(); LOG("launchTarget pid: %d", pid); task_t task; if (task_read_for_pid(mach_task_self(), pid, &task) != KERN_SUCCESS) { FAIL("task_read_for_pid() failed"); } LOG("launchTarget task: %u", task); // wait until process is up and has suspended itself if (!launchSuspended) { waitForTargetCheckin(pid); } LOG("task running"); return {process, task}; } static void testNotifications(task_t task, pid_t pid) { kern_return_t result; dyld_process_t process = dyld_process_create_for_task(task, &result); if (result != KERN_SUCCESS) { FAIL("dyld_process_create_for_task() should succeed, get return code %d", result); } if (process == NULL) { FAIL("dyld_process_create_for_task(task, 0) alwats return a value"); } // We do this instead of using a dispatch_semaphore to prevent priority inversions dispatch_block_t oneShotSemaphore = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, ^{}); dispatch_block_t mainReady = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, ^{}); dispatch_queue_t queue = dispatch_queue_create("com.apple.test.dyld_process_introspection.notifier", NULL); __block bool loadedCF = false; __block bool initializersReadyNotificationFired = false; __block bool mainReadyNotificationFired = false; __block uint32_t dlopenCount = 0; __block uint32_t dlcloseCount = 0; kern_return_t kr = KERN_SUCCESS; uint32_t mainHandle = dyld_process_register_for_event_notification(process, &kr, DYLD_REMOTE_EVENT_MAIN, queue, ^{ mainReadyNotificationFired = true; mainReady(); }); if (kr != KERN_SUCCESS) { FAIL("dyld_process_register_for_event_notification() should succeed, got return code %d", kr); } uint32_t cacheHandle = dyld_process_register_for_event_notification(process, &kr, DYLD_REMOTE_EVENT_BEFORE_INITIALIZERS, queue, ^{ initializersReadyNotificationFired = true; }); if (kr != KERN_SUCCESS) { FAIL("dyld_process_register_for_event_notification() should succeed, got return code %d", kr); } uint32_t updateHandle = dyld_process_register_for_image_notifications(process, &kr, queue, ^(dyld_image_t image, bool load) { const char* installname = dyld_image_get_installname(image); const char* filename = dyld_image_get_file_path(image); if ( load && installname && strstr(installname, "/CoreFoundation.framework/") != NULL ) { loadedCF = true; } if ( mainReadyNotificationFired && filename && strstr(filename, "/libfoo.bundle") != NULL ) { if (load) { ++dlopenCount; } else { ++dlcloseCount; } } // TODO: This should be explicitly send over the socket, but the test infra needs improvements if (dlcloseCount == 999) { oneShotSemaphore(); } }); if (kr != KERN_SUCCESS) { FAIL("dyld_process_register_for_image_notifications() should succeed, got return code %d", kr); } waitForTargetCheckin(pid); dispatch_block_wait(oneShotSemaphore, DISPATCH_TIME_FOREVER); if (!loadedCF) { FAIL("CF should be loaded"); } if (!initializersReadyNotificationFired) { FAIL("initializers ready notification should fire"); } if (!mainReadyNotificationFired) { FAIL("Main ready notification should fire"); } if (dlopenCount != 999) { FAIL("libfoo should be dlopen()ed 999 times"); } if (dlcloseCount != 999) { FAIL("libfoo should be dlclosed()ed 999 times"); } dyld_process_unregister_for_notification(process, mainHandle); dyld_process_unregister_for_notification(process, cacheHandle); dyld_process_unregister_for_notification(process, updateHandle); dyld_process_dispose(process); dispatch_release(queue); } int main(int argc, const char* argv[], const char* envp[], const char* apple[]) { signal(SIGUSR1, SIG_IGN); inspectProcess(mach_task_self(), false, false); //FIXME: The signals in these tests deadlock, need to rework them or move to sockets and a state machine // { // auto [process, task] = launchTarget(true); // testNotifications(task, process.get_pid()); // } // { // auto [process, task] = launchTarget(true); // inspectProcess(task, true, false); // } // { // auto [process, task] = launchTarget(false); // inspectProcess(task, false, true); // } PASS("SUCCESS"); } |