bool ksmach_i_VMStats(vm_statistics_data_t* const vmStats, vm_size_t* const pageSize) { kern_return_t kr; const mach_port_t hostPort = mach_host_self(); if((kr = host_page_size(hostPort, pageSize)) != KERN_SUCCESS) { KSLOG_ERROR("host_page_size: %s", mach_error_string(kr)); return false; } mach_msg_type_number_t hostSize = sizeof(*vmStats) / sizeof(natural_t); kr = host_statistics(hostPort, HOST_VM_INFO, (host_info_t)vmStats, &hostSize); if(kr != KERN_SUCCESS) { KSLOG_ERROR("host_statistics: %s", mach_error_string(kr)); return false; } return true; }
int64_t kscrs_addUserReport(const char* report, int reportLength) { pthread_mutex_lock(&g_mutex); int64_t currentID = g_nextUniqueID++; char crashReportPath[KSCRS_MAX_PATH_LENGTH]; getCrashReportPathByID(currentID, crashReportPath); int fd = open(crashReportPath, O_WRONLY | O_CREAT, 0644); if(fd < 0) { KSLOG_ERROR("Could not open file %s: %s", crashReportPath, strerror(errno)); goto done; } int bytesWritten = (int)write(fd, report, (unsigned)reportLength); if(bytesWritten < 0) { KSLOG_ERROR("Could not write to file %s: %s", crashReportPath, strerror(errno)); goto done; } else if(bytesWritten < reportLength) { KSLOG_ERROR("Expected to write %lull bytes to file %s, but only wrote %ll", crashReportPath, reportLength, bytesWritten); } done: if(fd >= 0) { close(fd); } pthread_mutex_unlock(&g_mutex); return currentID; }
bool ksfu_makePath(const char* absolutePath) { bool isSuccessful = false; char* pathCopy = strdup(absolutePath); for(char* ptr = pathCopy+1; *ptr != '\0';ptr++) { if(*ptr == '/') { *ptr = '\0'; if(mkdir(pathCopy, S_IRWXU) < 0 && errno != EEXIST) { KSLOG_ERROR("Could not create directory %s: %s", pathCopy, strerror(errno)); goto done; } *ptr = '/'; } } if(mkdir(pathCopy, S_IRWXU) < 0 && errno != EEXIST) { KSLOG_ERROR("Could not create directory %s: %s", pathCopy, strerror(errno)); goto done; } isSuccessful = true; done: free(pathCopy); return isSuccessful; }
static inline bool getThreadList(KSMachineContext* context) { const task_t thisTask = mach_task_self(); KSLOG_DEBUG("Getting thread list"); kern_return_t kr; thread_act_array_t threads; mach_msg_type_number_t actualThreadCount; if((kr = task_threads(thisTask, &threads, &actualThreadCount)) != KERN_SUCCESS) { KSLOG_ERROR("task_threads: %s", mach_error_string(kr)); return false; } KSLOG_TRACE("Got %d threads", context->threadCount); int threadCount = (int)actualThreadCount; int maxThreadCount = sizeof(context->allThreads) / sizeof(context->allThreads[0]); if(threadCount > maxThreadCount) { KSLOG_ERROR("Thread count %d is higher than maximum of %d", threadCount, maxThreadCount); threadCount = maxThreadCount; } for(int i = 0; i < threadCount; i++) { context->allThreads[i] = threads[i]; } context->threadCount = threadCount; for(mach_msg_type_number_t i = 0; i < actualThreadCount; i++) { mach_port_deallocate(thisTask, context->allThreads[i]); } vm_deallocate(thisTask, (vm_address_t)threads, sizeof(thread_t) * actualThreadCount); return true; }
bool kssysctl_getMacAddress(const char* const name, char* const macAddressBuffer) { // Based off http://iphonedevelopertips.com/device/determine-mac-address.html int mib[6] = { CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, (int)if_nametoindex(name) }; if(mib[5] == 0) { KSLOG_ERROR("Could not get interface index for %s: %s", name, strerror(errno)); return false; } size_t length; if(sysctl(mib, 6, NULL, &length, NULL, 0) != 0) { KSLOG_ERROR("Could not get interface data for %s: %s", name, strerror(errno)); return false; } void* ifBuffer = malloc(length); if(ifBuffer == NULL) { KSLOG_ERROR("Out of memory"); return false; } if(sysctl(mib, 6, ifBuffer, &length, NULL, 0) != 0) { KSLOG_ERROR("Could not get interface data for %s: %s", name, strerror(errno)); free(ifBuffer); return false; } struct if_msghdr* msgHdr = (struct if_msghdr*) ifBuffer; struct sockaddr_dl* sockaddr = (struct sockaddr_dl*) &msgHdr[1]; memcpy(macAddressBuffer, LLADDR(sockaddr), 6); free(ifBuffer); return true; }
/** Load the persistent state portion of a crash context. * * @param path The path to the file to read. * * @return true if the operation was successful. */ static bool loadState(const char* const path) { // Stop if the file doesn't exist. // This is expected on the first run of the app. const int fd = open(path, O_RDONLY); if(fd < 0) { return false; } close(fd); char* data; int length; if(!ksfu_readEntireFile(path, &data, &length, 50000)) { KSLOG_ERROR("%s: Could not load file", path); return false; } KSJSONDecodeCallbacks callbacks; callbacks.onBeginArray = onBeginArray; callbacks.onBeginObject = onBeginObject; callbacks.onBooleanElement = onBooleanElement; callbacks.onEndContainer = onEndContainer; callbacks.onEndData = onEndData; callbacks.onFloatingPointElement = onFloatingPointElement; callbacks.onIntegerElement = onIntegerElement; callbacks.onNullElement = onNullElement; callbacks.onStringElement = onStringElement; int errorOffset = 0; char stringBuffer[1000]; const int result = ksjson_decode(data, (int)length, stringBuffer, sizeof(stringBuffer), &callbacks, &g_state, &errorOffset); free(data); if(result != KSJSON_OK) { KSLOG_ERROR("%s, offset %d: %s", path, errorOffset, ksjson_stringForError(result)); return false; } return true; }
static bool deletePathContents(const char* path, bool deleteTopLevelPathAlso) { struct stat statStruct = {0}; if(stat(path, &statStruct) != 0) { KSLOG_ERROR("Could not stat %s: %s", path, strerror(errno)); return false; } if(S_ISDIR(statStruct.st_mode)) { char** entries = NULL; int entryCount = 0; dirContents(path, &entries, &entryCount); int bufferLength = KSFU_MAX_PATH_LENGTH; char* pathBuffer = malloc((unsigned)bufferLength); snprintf(pathBuffer, bufferLength, "%s/", path); char* pathPtr = pathBuffer + strlen(pathBuffer); int pathRemainingLength = bufferLength - (int)(pathPtr - pathBuffer); for(int i = 0; i < entryCount; i++) { char* entry = entries[i]; if(entry != NULL && canDeletePath(entry)) { strncpy(pathPtr, entry, pathRemainingLength); deletePathContents(pathBuffer, true); } } free(pathBuffer); freeDirListing(entries, entryCount); if(deleteTopLevelPathAlso) { ksfu_removeFile(path, false); } } else if(S_ISREG(statStruct.st_mode)) { ksfu_removeFile(path, false); } else { KSLOG_ERROR("Could not delete %s: Not a regular file.", path); return false; } return true; }
static int getReportCount() { int count = 0; DIR* dir = opendir(g_reportsPath); if(dir == NULL) { KSLOG_ERROR("Could not open directory %s", g_reportsPath); goto done; } struct dirent* ent; while((ent = readdir(dir)) != NULL) { if(getReportIDFromFilename(ent->d_name) > 0) { count++; } } done: if(dir != NULL) { closedir(dir); } return count; }
static int getReportIDs(int64_t* reportIDs, int count) { int index = 0; DIR* dir = opendir(g_reportsPath); if(dir == NULL) { KSLOG_ERROR("Could not open directory %s", g_reportsPath); goto done; } struct dirent* ent; while((ent = readdir(dir)) != NULL && index < count) { int64_t reportID = getReportIDFromFilename(ent->d_name); if(reportID > 0) { reportIDs[index++] = reportID; } } qsort(reportIDs, (unsigned)count, sizeof(reportIDs[0]), compareInt64); done: if(dir != NULL) { closedir(dir); } return index; }
void kscrash_setDoNotIntrospectClasses(const char** doNotIntrospectClasses, size_t length) { const char** oldClasses = crashContext()->config.introspectionRules.restrictedClasses; size_t oldClassesLength = crashContext()->config.introspectionRules.restrictedClassesCount; const char** newClasses = nil; size_t newClassesLength = 0; if(doNotIntrospectClasses != nil && length > 0) { newClassesLength = length; newClasses = malloc(sizeof(*newClasses) * newClassesLength); if(newClasses == nil) { KSLOG_ERROR("Could not allocate memory"); return; } for(size_t i = 0; i < newClassesLength; i++) { newClasses[i] = strdup(doNotIntrospectClasses[i]); } } crashContext()->config.introspectionRules.restrictedClasses = newClasses; crashContext()->config.introspectionRules.restrictedClassesCount = newClassesLength; if(oldClasses != nil) { for(size_t i = 0; i < oldClassesLength; i++) { free((void*)oldClasses[i]); } free(oldClasses); } }
/** Restore the original mach exception ports. */ void ksmachexc_i_restoreExceptionPorts(void) { KSLOG_DEBUG("Restoring original exception ports."); if(g_previousExceptionPorts.count == 0) { KSLOG_DEBUG("Original exception ports were already restored."); return; } const task_t thisTask = mach_task_self(); kern_return_t kr; // Reinstall old exception ports. for(mach_msg_type_number_t i = 0; i < g_previousExceptionPorts.count; i++) { KSLOG_TRACE("Restoring port index %d", i); kr = task_set_exception_ports(thisTask, g_previousExceptionPorts.masks[i], g_previousExceptionPorts.ports[i], g_previousExceptionPorts.behaviors[i], g_previousExceptionPorts.flavors[i]); if(kr != KERN_SUCCESS) { KSLOG_ERROR("task_set_exception_ports: %s", mach_error_string(kr)); } } KSLOG_DEBUG("Exception ports restored."); g_previousExceptionPorts.count = 0; }
void ksmach_init(void) { static volatile sig_atomic_t initialized = 0; if(!initialized) { kern_return_t kr; const task_t thisTask = mach_task_self(); thread_act_array_t threads; mach_msg_type_number_t numThreads; if((kr = task_threads(thisTask, &threads, &numThreads)) != KERN_SUCCESS) { KSLOG_ERROR("task_threads: %s", mach_error_string(kr)); return; } g_topThread = pthread_from_mach_thread_np(threads[0]); for(mach_msg_type_number_t i = 0; i < numThreads; i++) { mach_port_deallocate(thisTask, threads[i]); } vm_deallocate(thisTask, (vm_address_t)threads, sizeof(thread_t) * numThreads); initialized = true; } }
const char* kscpu_exceptionRegisterName(const int regNumber) { if(regNumber < kscpu_numExceptionRegisters()) { return g_exceptionRegisterNames[regNumber]; } KSLOG_ERROR("Invalid register number: %d", regNumber); return NULL; }
bool ksfu_readEntireFile(const char* const path, char** data, size_t* length) { struct stat st; if(stat(path, &st) < 0) { KSLOG_ERROR("Could not stat %s: %s", path, strerror(errno)); return false; } void* mem = NULL; int fd = open(path, O_RDONLY); if(fd < 0) { KSLOG_ERROR("Could not open %s: %s", path, strerror(errno)); return false; } mem = malloc((size_t)st.st_size); if(mem == NULL) { KSLOG_ERROR("Out of memory"); goto failed; } if(!ksfu_readBytesFromFD(fd, mem, (ssize_t)st.st_size)) { goto failed; } close(fd); *length = (size_t)st.st_size; *data = mem; return true; failed: close(fd); if(mem != NULL) { free(mem); } return false; }
void kscrashsentry_reportUserException(const char* name, const char* reason, const char* language, const char* lineOfCode, const char* stackTrace, bool terminateProgram) { if(g_context == NULL) { KSLOG_WARN("User-reported exception sentry is not installed. Exception has not been recorded."); } else { kscrashsentry_beginHandlingCrash(g_context); KSLOG_DEBUG("Suspending all threads"); kscrashsentry_suspendThreads(); KSLOG_DEBUG("Fetching call stack."); int callstackCount = 100; uintptr_t callstack[callstackCount]; callstackCount = backtrace((void**)callstack, callstackCount); if(callstackCount <= 0) { KSLOG_ERROR("backtrace() returned call stack length of %d", callstackCount); callstackCount = 0; } KSLOG_DEBUG("Filling out context."); g_context->crashType = KSCrashTypeUserReported; g_context->offendingThread = ksmach_thread_self(); g_context->registersAreValid = false; g_context->crashReason = reason; g_context->stackTrace = callstack; g_context->stackTraceLength = callstackCount; g_context->userException.name = name; g_context->userException.language = language; g_context->userException.lineOfCode = lineOfCode; g_context->userException.customStackTrace = stackTrace; KSLOG_DEBUG("Calling main crash handler."); g_context->onCrash(); if(terminateProgram) { kscrashsentry_uninstall(KSCrashTypeAll); kscrashsentry_resumeThreads(); abort(); } else { kscrashsentry_clearContext(g_context); kscrashsentry_resumeThreads(); } } }
void ksmc_addReservedThread(KSThread thread) { int nextIndex = g_reservedThreadsCount; if(nextIndex > g_reservedThreadsMaxIndex) { KSLOG_ERROR("Too many reserved threads (%d). Max is %d", nextIndex, g_reservedThreadsMaxIndex); return; } g_reservedThreads[g_reservedThreadsCount++] = thread; }
static void dirContents(const char* path, char*** entries, int* count) { DIR* dir = NULL; char** entryList = NULL; int entryCount = dirContentsCount(path); if(entryCount <= 0) { goto done; } dir = opendir(path); if(dir == NULL) { KSLOG_ERROR("Error reading directory %s: %s", path, strerror(errno)); goto done; } entryList = calloc((unsigned)entryCount, sizeof(char*)); struct dirent* ent; int index = 0; while((ent = readdir(dir))) { if(index >= entryCount) { KSLOG_ERROR("Contents of %s have been mutated", path); goto done; } entryList[index] = strdup(ent->d_name); index++; } done: if(dir != NULL) { closedir(dir); } if(entryList == NULL) { entryCount = 0; } *entries = entryList; *count = entryCount; }
bool ksfu_removeFile(const char* path, bool mustExist) { if(remove(path) < 0) { if(mustExist || errno != ENOENT) { KSLOG_ERROR("Could not delete %s: %s", path, strerror(errno)); } return false; } return true; }
bool ksfu_openBufferedWriter(KSBufferedWriter* writer, const char* const path, char* writeBuffer, int writeBufferLength) { writer->buffer = writeBuffer; writer->bufferLength = writeBufferLength; writer->position = 0; writer->fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0644); if(writer->fd < 0) { KSLOG_ERROR("Could not open crash report file %s: %s", path, strerror(errno)); return false; } return true; }
struct timeval kssysctl_timevalForName(const char* const name) { struct timeval value = {0}; size_t size = sizeof(value); if(0 != sysctlbyname(name, &value, &size, NULL, 0)) { KSLOG_ERROR("Could not get timeval value for %s: %s", name, strerror(errno)); } return value; }
uint64_t kscpu_registerValue(const KSMachineContext* const context, const int regNumber) { switch(regNumber) { case 0: return context->machineContext.__ss.__rax; case 1: return context->machineContext.__ss.__rbx; case 2: return context->machineContext.__ss.__rcx; case 3: return context->machineContext.__ss.__rdx; case 4: return context->machineContext.__ss.__rdi; case 5: return context->machineContext.__ss.__rsi; case 6: return context->machineContext.__ss.__rbp; case 7: return context->machineContext.__ss.__rsp; case 8: return context->machineContext.__ss.__r8; case 9: return context->machineContext.__ss.__r9; case 10: return context->machineContext.__ss.__r10; case 11: return context->machineContext.__ss.__r11; case 12: return context->machineContext.__ss.__r12; case 13: return context->machineContext.__ss.__r13; case 14: return context->machineContext.__ss.__r14; case 15: return context->machineContext.__ss.__r15; case 16: return context->machineContext.__ss.__rip; case 17: return context->machineContext.__ss.__rflags; case 18: return context->machineContext.__ss.__cs; case 19: return context->machineContext.__ss.__fs; case 20: return context->machineContext.__ss.__gs; } KSLOG_ERROR("Invalid register number: %d", regNumber); return 0; }
void ksmc_resumeEnvironment() { #if KSCRASH_HAS_THREADS_API KSLOG_DEBUG("Resuming environment."); kern_return_t kr; const task_t thisTask = mach_task_self(); const thread_t thisThread = (thread_t)ksthread_self(); thread_act_array_t threads; mach_msg_type_number_t numThreads; if((kr = task_threads(thisTask, &threads, &numThreads)) != KERN_SUCCESS) { KSLOG_ERROR("task_threads: %s", mach_error_string(kr)); return; } for(mach_msg_type_number_t i = 0; i < numThreads; i++) { thread_t thread = threads[i]; if(thread != thisThread && !isThreadInList(thread, g_reservedThreads, g_reservedThreadsCount)) { if((kr = thread_resume(thread)) != KERN_SUCCESS) { // Record the error and keep going. KSLOG_ERROR("thread_resume (%08x): %s", thread, mach_error_string(kr)); } } } for(mach_msg_type_number_t i = 0; i < numThreads; i++) { mach_port_deallocate(thisTask, threads[i]); } vm_deallocate(thisTask, (vm_address_t)threads, sizeof(thread_t) * numThreads); KSLOG_DEBUG("Resume complete."); #endif }
bool kssysctl_getProcessInfo(const int pid, struct kinfo_proc* const procInfo) { int cmd[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; size_t size = sizeof(*procInfo); if(0 != sysctl(cmd, sizeof(cmd)/sizeof(*cmd), procInfo, &size, NULL, 0)) { KSLOG_ERROR("Could not get the name for process %d: %s", pid, strerror(errno)); return false; } return true; }
struct timeval kssysctl_timeval(const int major_cmd, const int minor_cmd) { int cmd[2] = {major_cmd, minor_cmd}; struct timeval value = {0}; size_t size = sizeof(value); if(0 != sysctl(cmd, sizeof(cmd)/sizeof(*cmd), &value, &size, NULL, 0)) { KSLOG_ERROR("Could not get timeval value for %d,%d: %s", major_cmd, minor_cmd, strerror(errno)); } return value; }
/** Check if the current process is being traced or not. * * @return true if we're being traced. */ bool ksmach_isBeingTraced(void) { struct kinfo_proc procInfo; size_t structSize = sizeof(procInfo); int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()}; if(sysctl(mib, sizeof(mib)/sizeof(*mib), &procInfo, &structSize, NULL, 0) != 0) { KSLOG_ERROR("sysctl: %s", strerror(errno)); return false; } return (procInfo.kp_proc.p_flag & P_TRACED) != 0; }
uint64_t kscpu_exceptionRegisterValue(const KSMachineContext* const context, const int regNumber) { switch(regNumber) { case 0: return context->machineContext.__es.__trapno; case 1: return context->machineContext.__es.__err; case 2: return context->machineContext.__es.__faultvaddr; } KSLOG_ERROR("Invalid register number: %d", regNumber); return 0; }
bool ksmach_resumeAllThreadsExcept(thread_t* exceptThreads, int exceptThreadsCount) { kern_return_t kr; const task_t thisTask = mach_task_self(); const thread_t thisThread = ksmach_thread_self(); thread_act_array_t threads; mach_msg_type_number_t numThreads; if((kr = task_threads(thisTask, &threads, &numThreads)) != KERN_SUCCESS) { KSLOG_ERROR("task_threads: %s", mach_error_string(kr)); return false; } for(mach_msg_type_number_t i = 0; i < numThreads; i++) { thread_t thread = threads[i]; if(thread != thisThread && !isThreadInList(thread, exceptThreads, exceptThreadsCount)) { if((kr = thread_resume(thread)) != KERN_SUCCESS) { KSLOG_ERROR("thread_resume (%08x): %s", thread, mach_error_string(kr)); // Don't treat this as a fatal error. } } } for(mach_msg_type_number_t i = 0; i < numThreads; i++) { mach_port_deallocate(thisTask, threads[i]); } vm_deallocate(thisTask, (vm_address_t)threads, sizeof(thread_t) * numThreads); return true; }
bool ksfu_readBytesFromFD(const int fd, char* const bytes, int length) { char* pos = bytes; while(length > 0) { int bytesRead = (int)read(fd, pos, (unsigned)length); if(bytesRead == -1) { KSLOG_ERROR("Could not write to fd %d: %s", fd, strerror(errno)); return false; } length -= bytesRead; pos += bytesRead; } return true; }
uint64_t ksmach_exceptionRegisterValue(const STRUCT_MCONTEXT_L* const machineContext, const int regNumber) { switch(regNumber) { case 0: return machineContext->__es.__exception; case 1: return machineContext->__es.__esr; case 2: return machineContext->__es.__far; } KSLOG_ERROR("Invalid register number: %d", regNumber); return 0; }
bool ksmach_fillState(const thread_t thread, const thread_state_t state, const thread_state_flavor_t flavor, const mach_msg_type_number_t stateCount) { mach_msg_type_number_t stateCountBuff = stateCount; kern_return_t kr; kr = thread_get_state(thread, flavor, state, &stateCountBuff); if(kr != KERN_SUCCESS) { KSLOG_ERROR("thread_get_state: %s", mach_error_string(kr)); return false; } return true; }