Files
meeting-assistant/tools/ci/native-process-probe/NativeProcessProbe.c
T

107 lines
7.9 KiB
C

// Disposable pre-SDK observation boundary. No control task port or process writes.
#include <errno.h>
#include <limits.h>
#include <libproc.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <mach/i386/thread_status.h>
#include <mach-o/dyld_images.h>
#include <stddef.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <unistd.h>
// Private exported BSD API/selector, verified against XNU10063.141.1. Its
// return is BSD int + errno, not a Mach kern_return_t. See manifest sources.
extern int task_read_for_pid(mach_port_name_t, int, mach_port_name_t *) __attribute__((weak_import));
#define READ_ONLY_DARWIN_ROLE 6
static unsigned output_bytes;
static void emit(const char *format, ...) {
char line[256]; va_list args; va_start(args, format);
int length = vsnprintf(line, sizeof line, format, args); va_end(args);
if (length < 0 || length >= (int)sizeof line || output_bytes + (unsigned)length > 32768) exit(70);
output_bytes += (unsigned)length; fwrite(line, 1, (size_t)length, stderr);
}
static void phase(const char *name, const char *point) { emit("[phase] %s %s\n", name, point); }
int main(int argc, char **argv) {
setvbuf(stderr, NULL, _IONBF, 0);
emit("[probe-entry] self=%d architecture=%s snapshot-only=true\n", getpid(),
#if defined(__x86_64__)
"x86_64"
#else
"other"
#endif
);
if (argc != 3) { emit("[invalid-pid] require target and expected-parent decimal PIDs greater than1\n"); return 64; }
long parsed[2];
for (int argument = 1; argument <= 2; ++argument) {
if (!argv[argument][0]) { emit("[invalid-pid] empty PID\n"); return 64; }
for (const char *p = argv[argument]; *p; ++p) if (*p < '0' || *p > '9') { emit("[invalid-pid] decimal digits required\n"); return 64; }
errno = 0; char *end; parsed[argument - 1] = strtol(argv[argument], &end, 10);
if (errno || *end || parsed[argument - 1] <= 1 || parsed[argument - 1] > INT_MAX) { emit("[invalid-pid] PID outside permitted numeric range\n"); return 64; }
}
int pid = (int)parsed[0], expected_parent = (int)parsed[1]; struct proc_bsdinfo bsd = {0};
phase("proc_pidinfo", "before"); errno = 0;
int bytes = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &bsd, sizeof bsd); int bsd_errno = errno;
emit("[phase] proc_pidinfo after return=%d errno=%d\n", bytes, bsd_errno);
if (bytes != sizeof bsd) { emit("[bsd-unavailable] identity was not established\n"); return 66; }
emit("[bsd] pid=%u ppid=%u flags=0x%x nice=%d status=%u\n", bsd.pbi_pid, bsd.pbi_ppid, bsd.pbi_flags, bsd.pbi_nice, bsd.pbi_status);
if (bsd.pbi_pid != (unsigned)pid || bsd.pbi_ppid != (unsigned)expected_parent) { emit("[ownership-rejected] target=%d expected-parent=%d actual-pid=%u actual-parent=%u\n", pid, expected_parent, bsd.pbi_pid, bsd.pbi_ppid); return 65; }
phase("getpriority-role", "before"); errno = 0;
int role = getpriority(READ_ONLY_DARWIN_ROLE, (id_t)pid); int role_errno = errno;
emit("[role] selector=6 value=%d errno=%d\n", role, role_errno); phase("getpriority-role", "after");
struct proc_taskinfo taskinfo = {0}; phase("proc_pidinfo-task", "before"); errno = 0;
bytes = proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &taskinfo, sizeof taskinfo); int taskinfo_errno = errno;
emit("[phase] proc_pidinfo-task after return=%d errno=%d\n", bytes, taskinfo_errno);
if (bytes == sizeof taskinfo) {
emit("[bsd-task] total-user-raw=%llu total-system-raw=%llu threads-user-raw=%llu threads-system-raw=%llu\n", taskinfo.pti_total_user, taskinfo.pti_total_system, taskinfo.pti_threads_user, taskinfo.pti_threads_system);
emit("[bsd-task] threads=%d running=%d policy=%d priority=%d faults=%d pageins=%d virtual-bytes=%llu resident-bytes=%llu\n", taskinfo.pti_threadnum, taskinfo.pti_numrunning, taskinfo.pti_policy, taskinfo.pti_priority, taskinfo.pti_faults, taskinfo.pti_pageins, taskinfo.pti_virtual_size, taskinfo.pti_resident_size);
}
if (!task_read_for_pid) {
emit("[task-read-unavailable] optional private symbol absent; public BSD snapshot remains observational\n");
emit("[probe-end] snapshot-only=true qualified-readiness=false\n"); return 0;
}
mach_port_t task = MACH_PORT_NULL; phase("task_read_for_pid", "before"); errno = 0;
int read_result = task_read_for_pid(mach_task_self(), pid, &task); int read_errno = errno;
emit("[task-read] return=%d errno=%d port=0x%x\n", read_result, read_errno, task); phase("task_read_for_pid", "after");
if (read_result != 0 || task == MACH_PORT_NULL) {
emit("[mach-unavailable] read-only capability denied; BSD snapshot remains observational\n");
if (task != MACH_PORT_NULL) mach_port_deallocate(mach_task_self(), task);
emit("[probe-end] snapshot-only=true qualified-readiness=false\n");
return 0;
}
emit("[mach-capability] read-only=true; unsuspended snapshots may be incomplete\n");
task_dyld_info_data_t dyld = {0}; mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
phase("task_info-dyld", "before"); kern_return_t kr = task_info(task, TASK_DYLD_INFO, (task_info_t)&dyld, &count);
emit("[phase] task_info-dyld after kern=%d count=%u\n", kr, count);
size_t prefix = offsetof(struct dyld_all_image_infos, jitInfo);
if (kr == KERN_SUCCESS && count == TASK_DYLD_INFO_COUNT && dyld.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_64 && dyld.all_image_info_size >= prefix) {
struct dyld_all_image_infos info = {0}; mach_vm_size_t received = 0; phase("dyld-prefix-read", "before");
kr = mach_vm_read_overwrite(task, dyld.all_image_info_addr, prefix, (mach_vm_address_t)(uintptr_t)&info, &received);
emit("[phase] dyld-prefix-read after kern=%d bytes=%llu\n", kr, (unsigned long long)received);
if (kr == KERN_SUCCESS && received == prefix) emit("[dyld] version=%u images=%u array=0x%llx libSystemInitialized=%d dyld=0x%llx array-null-is-pending=true\n", info.version, info.infoArrayCount, (unsigned long long)(uintptr_t)info.infoArray, info.version >= 2 ? info.libSystemInitialized : -1, info.version >= 2 ? (unsigned long long)(uintptr_t)info.dyldImageLoadAddress : 0);
}
thread_act_array_t threads = NULL; mach_msg_type_number_t thread_count = 0;
phase("task_threads", "before"); kr = task_threads(task, &threads, &thread_count);
emit("[phase] task_threads after kern=%d count=%u observed-limit=32\n", kr, thread_count);
if (kr == KERN_SUCCESS) {
for (unsigned i = 0; i < thread_count && i < 32; ++i) {
thread_basic_info_data_t basic = {0}; count = THREAD_BASIC_INFO_COUNT; phase("thread_info", "before");
kr = thread_info(threads[i], THREAD_BASIC_INFO, (thread_info_t)&basic, &count);
emit("[phase] thread_info after index=%u kern=%d count=%u\n", i, kr, count);
if (kr == KERN_SUCCESS && count == THREAD_BASIC_INFO_COUNT) emit("[thread-basic] index=%u run-state=%d policy=%d cpu=%d user=%d.%06d system=%d.%06d\n", i, basic.run_state, basic.policy, basic.cpu_usage, basic.user_time.seconds, basic.user_time.microseconds, basic.system_time.seconds, basic.system_time.microseconds);
x86_thread_state64_t registers = {0}; count = x86_THREAD_STATE64_COUNT; phase("thread_get_state-x86_64", "before");
kr = thread_get_state(threads[i], x86_THREAD_STATE64, (thread_state_t)&registers, &count);
emit("[phase] thread_get_state-x86_64 after index=%u kern=%d count=%u\n", i, kr, count);
if (kr == KERN_SUCCESS && count == x86_THREAD_STATE64_COUNT) emit("[thread-registers] index=%u rip=0x%llx rbp=0x%llx rsp=0x%llx\n", i, registers.__rip, registers.__rbp, registers.__rsp);
}
for (unsigned i = 0; i < thread_count; ++i) mach_port_deallocate(mach_task_self(), threads[i]);
vm_deallocate(mach_task_self(), (vm_address_t)threads, thread_count * sizeof *threads);
}
mach_port_deallocate(mach_task_self(), task);
emit("[probe-end] snapshot-only=true qualified-readiness=false\n"); return 0;
}