#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>
extern char** environ;
#if __x86_64__
cpu_type_t otherArch[] = { CPU_TYPE_I386 };
#elif __i386__
cpu_type_t otherArch[] = { CPU_TYPE_X86_64 };
#elif __arm64__
cpu_type_t otherArch[] = { CPU_TYPE_ARM };
#elif __arm__
cpu_type_t otherArch[] = { CPU_TYPE_ARM64 };
#endif
struct task_and_pid {
pid_t pid;
task_t task;
};
static struct task_and_pid launchTest(const char* testProgPath, const char* arg1, bool launchOtherArch, bool launchSuspended)
{
posix_spawnattr_t attr = 0;
if ( posix_spawnattr_init(&attr) != 0 ) {
printf("[FAIL] dyld_process_info_notify posix_spawnattr_init()\n");
exit(0);
}
if ( launchSuspended ) {
if ( posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED) != 0 ) {
printf("[FAIL] dyld_process_info_notify POSIX_SPAWN_START_SUSPENDED\n");
exit(0);
}
}
if ( launchOtherArch ) {
size_t copied;
if ( posix_spawnattr_setbinpref_np(&attr, 1, otherArch, &copied) != 0 ) {
printf("[FAIL] dyld_process_info_notify posix_spawnattr_setbinpref_np()\n");
exit(0);
}
}
struct task_and_pid child;
const char* argv[] = { testProgPath, arg1, NULL };
int psResult = posix_spawn(&child.pid, testProgPath, NULL, &attr, (char**)argv, environ);
if ( psResult != 0 ) {
printf("[FAIL] dyld_process_info_notify posix_spawn(%s) failed, err=%d\n", testProgPath, psResult);
exit(0);
}
if (posix_spawnattr_destroy(&attr) != 0) {
printf("[FAIL] dyld_process_info_notify posix_spawnattr_destroy()\n");
exit(0);
}
if ( task_for_pid(mach_task_self(), child.pid, &child.task) != KERN_SUCCESS ) {
printf("[FAIL] dyld_process_info_notify task_for_pid()\n");
(void)kill(child.pid, SIGKILL);
exit(0);
}
return child;
}
static void killTest(struct task_and_pid tp) {
int r = kill(tp.pid, SIGKILL);
waitpid(tp.pid, &r, 0);
}
static void wait_util_task_suspended(task_t task)
{
struct task_basic_info info;
do {
unsigned count = TASK_BASIC_INFO_COUNT;
kern_return_t kr = task_info(task, TASK_BASIC_INFO, (task_info_t)&info, &count);
sleep(1);
} while ( info.suspend_count == 0 );
}
static bool monitor(struct task_and_pid tp, bool disconnectEarly, bool attachLate)
{
kern_return_t kr;
__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;
dispatch_semaphore_t taskDone = dispatch_semaphore_create(0);
dispatch_queue_t serviceQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
unsigned count = 0;
dyld_process_info_notify handle;
do {
handle = _dyld_process_info_notify(tp.task, serviceQueue,
^(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 ) {
gotEarlyNotice = true;
dispatch_semaphore_signal(taskDone);
}
}
},
^{
gotTerminationNotice = true;
dispatch_semaphore_signal(taskDone);
},
&kr);
++count;
if ( handle == NULL )
fprintf(stderr, "_dyld_process_info_notify() returned NULL, result=%d, count=%d\n", kr, count);
} while ( (handle == NULL) && (count < 5) );
if ( handle == NULL ) {
return false;
}
if (!attachLate) {
_dyld_process_info_notify_main(handle, ^{
gotMainNotice = true;
if ( !sawMainExecutable || !sawlibSystem )
gotMainNoticeBeforeAllInitialDylibs = true;
});
} else {
wait_util_task_suspended(tp.task);
}
kill(tp.pid, SIGCONT);
bool gotSignal = (dispatch_semaphore_wait(taskDone, dispatch_time(DISPATCH_TIME_NOW, 10LL * NSEC_PER_SEC)) == 0);
_dyld_process_info_notify_release(handle);
if ( !gotSignal ) {
fprintf(stderr, "did not get exit signal\n");
return false;
}
if (!attachLate) {
if ( !sawMainExecutable ) {
fprintf(stderr, "did not get load notification of main executable\n");
return false;
}
if ( !gotMainNotice ) {
fprintf(stderr, "did not get notification of main()\n");
return false;
}
if ( gotMainNoticeBeforeAllInitialDylibs ) {
fprintf(stderr, "notification of main() arrived before all initial dylibs\n");
return false;
}
if ( gotFooNoticeBeforeMain ) {
fprintf(stderr, "notification of main() arrived after libfoo load notice\n");
return false;
}
if ( !sawlibSystem ) {
fprintf(stderr, "did not get load notification of libSystem\n");
return false;
}
}
if ( disconnectEarly ) {
if ( libFooLoadCount != 1 ) {
fprintf(stderr, "got %d load notifications about libFoo instead of 1\n", libFooLoadCount);
return false;
}
if ( libFooUnloadCount != 0 ) {
fprintf(stderr, "got %d unload notifications about libFoo instead of 1\n", libFooUnloadCount);
return false;
}
}
else {
if ( libFooLoadCount != 3 ) {
fprintf(stderr, "got %d load notifications about libFoo instead of 3\n", libFooLoadCount);
return false;
}
if ( libFooUnloadCount != 3 ) {
fprintf(stderr, "got %d unload notifications about libFoo instead of 3\n", libFooUnloadCount);
return false;
}
}
return true;
}
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];
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) {
},
^{
},
&kr);
if ( handles[i] == NULL ) {
if ( i == 8 ) {
_dyld_process_info_notify_release(handles[4]);
handles[4] = NULL;
}
else {
fprintf(stderr, "_dyld_process_info_notify() returned NULL and kern_result=%d, on count=%d\n", kr, i);
killTest(tp);
exit(0);
}
}
}
for (int i=0; i < 10; ++i) {
if ( handles[i] != NULL ) {
_dyld_process_info_notify_release(handles[i]);
}
}
dispatch_release(serviceQueue);
}
static bool testSelfAttach(void) {
__block bool retval = false;
kern_return_t kr = KERN_SUCCESS;
dispatch_queue_t serviceQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dyld_process_info_notify handle = _dyld_process_info_notify(mach_task_self(), serviceQueue,
^(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path) {
if ( strstr(path, "/libfoo.dylib") != NULL ) {
retval = true;
}
},
^{},
&kr);
if ( handle == NULL ) {
fprintf(stderr, "_dyld_process_info_notify() returned NULL, result=%d\n", kr);
}
void* h = dlopen("./libfoo.dylib", 0);
dlclose(h);
return retval;
}
int main(int argc, const char* argv[])
{
if ( argc < 2 ) {
printf("[FAIL] dyld_process_info_notify missing argument\n");
exit(0);
}
const char* testProgPath = argv[1];
dispatch_async(dispatch_get_main_queue(), ^{
struct task_and_pid child;
printf("[BEGIN] dyld_process_info_notify laucnh suspended (same arch)\n");
child = launchTest(testProgPath, "", false, true);
if ( ! monitor(child, false, false) ) {
printf("[FAIL] dyld_process_info_notify launch suspended missed some notifications\n");
killTest(child);
exit(0);
}
killTest(child);
printf("[PASS] dyld_process_info_notify laucnh suspended (same arch)\n");
printf("[BEGIN] dyld_process_info_notify laucnh suspend-in-main (same arch)\n");
child = launchTest(testProgPath, "suspend-in-main", false, false);
validateMaxNotifies(child);
if ( ! monitor(child, false, true) ) {
printf("[FAIL] dyld_process_info_notify launch suspend-in-main missed some notifications\n");
killTest(child);
exit(0);
}
killTest(child);
printf("[PASS] dyld_process_info_notify laucnh suspend-in-main (same arch)\n");
#if 0
printf("[BEGIN] dyld_process_info_notify laucnh suspended (other arch)\n");
child = launchTest(testProgPath, "", true, true);
if ( ! monitor(child, false, false) ) {
printf("[FAIL] dyld_process_info_notify launch suspended other arch missed some notifications\n");
killTest(child);
exit(0);
}
killTest(child);
printf("[PASS] dyld_process_info_notify laucnh suspended (other arch)\n");
printf("[BEGIN] dyld_process_info_notify laucnh suspend-in-main (other arch)\n");
child = launchTest(testProgPath, "suspend-in-main", true, false);
if ( ! monitor(child, false, true) ) {
printf("[FAIL] dyld_process_info_notify launch other arch suspend-in-main missed some notifications\n");
killTest(child);
exit(0);
}
killTest(child);
printf("[PASS] dyld_process_info_notify laucnh suspend-in-main (other arch)\n");
#endif
printf("[BEGIN] dyld_process_info_notify disconnect\n");
child = launchTest(testProgPath, "", false, true);
if ( ! monitor(child, true, false) ) {
printf("[FAIL] dyld_process_info_notify connect/disconnect missed some notifications\n");
killTest(child);
exit(0);
}
killTest(child);
printf("[PASS] dyld_process_info_notify disconnect\n");
printf("[BEGIN] dyld_process_info_notify self-attach\n");
if (! testSelfAttach() ) {
printf("[FAIL] dyld_process_info_notify self notification\n");
}
printf("[PASS] dyld_process_info_notify self-attach\n");
exit(0);
});
dispatch_main();
}