KSCrashType kscrash_setHandlingCrashTypes(KSCrashType crashTypes) { if((crashTypes & KSCrashTypeDebuggerUnsafe) && ksmach_isBeingTraced()) { KSLOGBASIC_WARN("KSCrash: App is running in a debugger. The following crash types have been disabled:"); KSCrashType disabledCrashTypes = crashTypes & KSCrashTypeDebuggerUnsafe; for(int i = 0; i < 31; i++) { KSCrashType type = 1 << i; if(disabledCrashTypes & type) { KSLOGBASIC_WARN("* %s", kscrashtype_name(type)); } } crashTypes &= KSCrashTypeDebuggerSafe; } KSCrash_Context* context = crashContext(); context->config.handlingCrashTypes = crashTypes; if(g_installed) { kscrashsentry_uninstall(~crashTypes); crashTypes = kscrashsentry_installWithContext(&context->crash, crashTypes, kscrash_i_onCrash); } return crashTypes; }
bool kscrashsentry_installMachHandler(KSCrash_SentryContext* const context) { KSLOG_DEBUG("Installing mach exception handler."); bool attributes_created = false; pthread_attr_t attr; kern_return_t kr; int error; const task_t thisTask = mach_task_self(); exception_mask_t mask = EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_SOFTWARE | EXC_MASK_BREAKPOINT; if(g_installed) { KSLOG_DEBUG("Exception handler already installed."); return true; } g_installed = 1; if(ksmach_isBeingTraced()) { // Different debuggers hook into different exception types. // For example, GDB uses EXC_BAD_ACCESS for single stepping, // and LLDB uses EXC_SOFTWARE to stop a debug session. // Because of this, it's safer to not hook into the mach exception // system at all while being debugged. KSLOG_WARN("Process is being debugged. Not installing handler."); goto failed; } g_context = context; KSLOG_DEBUG("Backing up original exception ports."); kr = task_get_exception_ports(thisTask, mask, g_previousExceptionPorts.masks, &g_previousExceptionPorts.count, g_previousExceptionPorts.ports, g_previousExceptionPorts.behaviors, g_previousExceptionPorts.flavors); if(kr != KERN_SUCCESS) { KSLOG_ERROR("task_get_exception_ports: %s", mach_error_string(kr)); goto failed; } if(g_exceptionPort == MACH_PORT_NULL) { KSLOG_DEBUG("Allocating new port with receive rights."); kr = mach_port_allocate(thisTask, MACH_PORT_RIGHT_RECEIVE, &g_exceptionPort); if(kr != KERN_SUCCESS) { KSLOG_ERROR("mach_port_allocate: %s", mach_error_string(kr)); goto failed; } KSLOG_DEBUG("Adding send rights to port."); kr = mach_port_insert_right(thisTask, g_exceptionPort, g_exceptionPort, MACH_MSG_TYPE_MAKE_SEND); if(kr != KERN_SUCCESS) { KSLOG_ERROR("mach_port_insert_right: %s", mach_error_string(kr)); goto failed; } } KSLOG_DEBUG("Installing port as exception handler."); kr = task_set_exception_ports(thisTask, mask, g_exceptionPort, EXCEPTION_DEFAULT, THREAD_STATE_NONE); if(kr != KERN_SUCCESS) { KSLOG_ERROR("task_set_exception_ports: %s", mach_error_string(kr)); goto failed; } KSLOG_DEBUG("Creating secondary exception thread (suspended)."); pthread_attr_init(&attr); attributes_created = true; pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); error = pthread_create(&g_secondaryThread, &attr, &ksmachexc_i_handleExceptions, kThreadSecondary); if(error != 0) { KSLOG_ERROR("pthread_create_suspended_np: %s", strerror(error)); goto failed; } context->reservedThreads[KSCrashReservedThreadTypeMachSecondary] = pthread_mach_thread_np(g_secondaryThread); KSLOG_DEBUG("Creating primary exception thread."); error = pthread_create(&g_primaryThread, &attr, &ksmachexc_i_handleExceptions, kThreadPrimary); if(error != 0) { KSLOG_ERROR("pthread_create: %s", strerror(error)); goto failed; } pthread_attr_destroy(&attr); context->reservedThreads[KSCrashReservedThreadTypeMachPrimary] = pthread_mach_thread_np(g_primaryThread); KSLOG_DEBUG("Mach exception handler installed."); return true; failed: KSLOG_DEBUG("Failed to install mach exception handler."); if(attributes_created) { pthread_attr_destroy(&attr); } kscrashsentry_uninstallMachHandler(); return false; }
bool kscrash_install(const char* const crashReportFilePath, const char* const recrashReportFilePath, const char* const stateFilePath, const char* const crashID, const char* const userInfoJSON, unsigned int zombieCacheSize, const bool printTraceToStdout, const KSReportWriteCallback onCrashNotify) { KSLOG_DEBUG("Installing crash reporter."); KSLOG_TRACE("reportFilePath = %s", reportFilePath); KSLOG_TRACE("secondaryReportFilePath = %s", secondaryReportFilePath); KSLOG_TRACE("stateFilePath = %s", stateFilePath); KSLOG_TRACE("crashID = %s", crashID); KSLOG_TRACE("userInfoJSON = %p", userInfoJSON); KSLOG_TRACE("zombieCacheSize = %d", zombieCacheSize); KSLOG_TRACE("printTraceToStdout = %d", printTraceToStdout); KSLOG_TRACE("onCrashNotify = %p", onCrashNotify); static volatile sig_atomic_t initialized = 0; if(!initialized) { initialized = 1; g_stateFilePath = strdup(stateFilePath); g_crashReportFilePath = strdup(crashReportFilePath); g_recrashReportFilePath = strdup(recrashReportFilePath); KSCrash_Context* context = crashContext(); context->crash.onCrash = kscrash_i_onCrash; if(ksmach_isBeingTraced()) { KSLOGBASIC_WARN("KSCrash: App is running in a debugger. Crash handlers have been disabled for the sanity of all."); } else if(kscrashsentry_installWithContext(&context->crash, KSCrashTypeAll) == 0) { KSLOG_ERROR("Failed to install any handlers"); } if(!kscrashstate_init(g_stateFilePath, &context->state)) { KSLOG_ERROR("Failed to initialize persistent crash state"); } context->state.appLaunchTime = mach_absolute_time(); context->config.printTraceToStdout = printTraceToStdout; context->config.systemInfoJSON = kssysteminfo_toJSON(); context->config.processName = kssystemInfo_copyProcessName(); kscrash_setUserInfoJSON(userInfoJSON); context->config.crashID = strdup(crashID); context->config.onCrashNotify = onCrashNotify; if(zombieCacheSize > 0) { KSLOG_DEBUG("zombieCacheSize > 0. Installing zombie handler."); kszombie_install(zombieCacheSize); } KSLOG_DEBUG("Installation complete."); return true; } KSLOG_ERROR("Called more than once"); return false; }