简介

首要功用

  • 支撑离线符号化的设备上符号化(关于部分iOS体系上许多函数被编辑过的来说很有必要)
  • 生成包括完好字段的Apple陈述
  • 处理了部分只出现在mach反常的过错,例如栈溢出
  • 追踪一个未被捕获的C++反常的真实原因
  • Crash捕获本身处理Crash(或许在用户Crash捕获回调中)
  • 检测僵尸目标(已开释)的拜访测验
  • 在僵尸目标或许内存损坏的情况下恢复丢失的NSException消息
  • 检测寄存器和仓库中目标(C字符串和Objective-C目标,包括ivars)
  • 提取被反常引证的目标信息(例如”unrecognized selector sent to instance 0xa26d9a0″)
  • 插件化的服务器陈述架构能够更好的适应任何API服务
  • 获取仓库内容
  • 诊断崩溃原因
  • 除Apple Crash Report外运用JSON格局记载更多信息
  • 支撑包括自定义日志信息(在Crash前和Crash中)

捕获Crash类型

  • Mach内核反常
  • 类Unix体系信号
  • C++反常
  • Objective-C反常
  • 主线程死锁(实验性)
  • 自定义Crash(例如来自脚本言语)

原理

结构

APM - iOS Crash监控 KSCrash代码解析

  • API层

首要供给了对外的接口

  • Installation层

不同Installation的类依靠的sink过滤和上报的类不同,库中供给了规范的运用Url上报的服务器,运用Console打印出来,Email发送Crash Report等多种不同的发送办法

  • Crash Recording层

Monitor

KSCrashMonitor相当于Monitor Manager担任按照Type枚举类型初始化和发动对应的Monitor,装备回调获取Context类型的数据

各个Monitor担任抓取Mach_Exception,Signal,NSException,C++ Exception,User用户自定义等反常,并获取System,AppState等信息

Writer

首要是把获取到的Crash信息写到本地。KSCrashDoctor供给了反常剖析的功用

  • Crash Reporting层

担任对Crash Report的过滤和上报

Crash Recording层考虑到反常监听以及功能问题首要由C言语完成,Installation和Reporting运用OC的完成,充分利用了承继联系和办法重载

发动

Crash监控的发动尽量放在最早的阶段

APM - iOS Crash监控 KSCrash代码解析

KSCrashInstallation有多个子类,不同的子类在上报办法sink上会有不同完成,选择一个Installation进行创立。

  • KSCrashInstallation
    • KSCrashInstallationEmail
    • KSCrashInstallationConsole
    • KSCrashInstallationHockey
    • KSCrashInstallationQuincy
    • KSCrashInstallationStandard
    • KSCrashInstallationVictory
    // Create an installation (choose one)
//    KSCrashInstallation* installation = [self makeStandardInstallation];
    KSCrashInstallation* installation = [self makeEmailInstallation];
//    KSCrashInstallation* installation = [self makeHockeyInstallation];
//    KSCrashInstallation* installation = [self makeQuincyInstallation];
//    KSCrashInstallation *installation = [self makeVictoryInstallation];

KSCrashInstallation -install

- (void) install
{
    KSCrash* handler = [KSCrash sharedInstance];
    @synchronized(handler)
    {
        g_crashHandlerData = self.crashHandlerData;
        handler.onCrash = crashCallback;
        [handler install];
    }
}

KSCrash

- (BOOL) install
{
    _monitoring = kscrash_install(self.bundleName.UTF8String,
                                          self.basePath.UTF8String);
    if(self.monitoring == 0)
    {
        return false;
    }
    return true;
}

KSCrashC

  • 依据appName和installPath初始化途径目录
KSCrashMonitorType kscrash_install(const char* appName, const char* const installPath)
{
    KSLOG_DEBUG("Installing crash reporter.");
    if(g_installed)
    {
        KSLOG_DEBUG("Crash reporter already installed.");
        return g_monitoring;
    }
    g_installed = 1;
    char path[KSFU_MAX_PATH_LENGTH];
    snprintf(path, sizeof(path), "%s/Reports", installPath);
    ksfu_makePath(path);
    kscrs_initialize(appName, path);
    snprintf(path, sizeof(path), "%s/Data", installPath);
    ksfu_makePath(path);
    snprintf(path, sizeof(path), "%s/Data/CrashState.json", installPath);
    kscrashstate_initialize(path);
    snprintf(g_consoleLogPath, sizeof(g_consoleLogPath), "%s/Data/ConsoleLog.txt", installPath);
    if(g_shouldPrintPreviousLog)
    {
        printPreviousLog(g_consoleLogPath);
    }
    kslog_setLogFilename(g_consoleLogPath, true);
    ksccd_init(60);
    kscm_setEventCallback(onCrash);
    KSCrashMonitorType monitors = kscrash_setMonitoring(g_monitoring);
    KSLOG_DEBUG("Installation complete.");
    notifyOfBeforeInstallationState();
    return monitors;
}
  • 设置不同类型监控monitors
KSCrashMonitorType kscrash_setMonitoring(KSCrashMonitorType monitors)
{
    g_monitoring = monitors;
    if(g_installed)
    {
        kscm_setActiveMonitors(monitors);
        return kscm_getActiveMonitors();
    }
    // Return what we will be monitoring in future.
    return g_monitoring;
}
  • monitor类型如枚举所示
/** Various aspects of the system that can be monitored:
 * - Mach kernel exception
 * - Fatal signal
 * - Uncaught C++ exception
 * - Uncaught Objective-C NSException
 * - Deadlock on the main thread
 * - User reported custom exception
 */
typedef enum
{
    /* Captures and reports Mach exceptions. */
    KSCrashMonitorTypeMachException      = 0x01,
    /* Captures and reports POSIX signals. */
    KSCrashMonitorTypeSignal             = 0x02,
    /* Captures and reports C++ exceptions.
     * Note: This will slightly slow down exception processing.
     */
    KSCrashMonitorTypeCPPException       = 0x04,
    /* Captures and reports NSExceptions. */
    KSCrashMonitorTypeNSException        = 0x08,
    /* Detects and reports a deadlock in the main thread. */
    KSCrashMonitorTypeMainThreadDeadlock = 0x10,
    /* Accepts and reports user-generated exceptions. */
    KSCrashMonitorTypeUserReported       = 0x20,
    /* Keeps track of and injects system information. */
    KSCrashMonitorTypeSystem             = 0x40,
    /* Keeps track of and injects application state. */
    KSCrashMonitorTypeApplicationState   = 0x80,
    /* Keeps track of zombies, and injects the last zombie NSException. */
    KSCrashMonitorTypeZombie             = 0x100,
} KSCrashMonitorType;

监控

KSCrashMonitor

相当于Monitor Manager的角色,依据装备的枚举类型敞开对应monitor,并从monitor回调获取到对应的Crash数据和APP及设备的辅助信息

APM - iOS Crash监控 KSCrash代码解析

KSCrashMonitor对外供给的才能

  • 依据枚举值打开monitor
  • 返回正在运转的monitor
  • 设置回调
#ifndef HDR_KSCrashMonitor_h
#define HDR_KSCrashMonitor_h
#ifdef __cplusplus
extern "C" {
#endif
#include "KSCrashMonitorType.h"
#include "KSThread.h"
#include <stdbool.h>
struct KSCrash_MonitorContext;
// ============================================================================
#pragma mark - External API -
// ============================================================================
/** Set which monitors are active.
 *
 * @param monitorTypes Which monitors should be active.
 */
void kscm_setActiveMonitors(KSCrashMonitorType monitorTypes);
/** Get the currently active monitors.
 */
KSCrashMonitorType kscm_getActiveMonitors(void);
/** Set the callback to call when an event is captured.
 *
 * @param onEvent Called whenever an event is captured.
 */
void kscm_setEventCallback(void (*onEvent)(struct KSCrash_MonitorContext* monitorContext));
// ============================================================================
#pragma mark - Internal API -
// ============================================================================
typedef struct
{
    void (*setEnabled)(bool isEnabled);
    bool (*isEnabled)(void);
    void (*addContextualInfoToEvent)(struct KSCrash_MonitorContext* eventContext);
} KSCrashMonitorAPI;
/** Notify that a fatal exception has been captured.
 *  This allows the system to take appropriate steps in preparation.
 *
 * @oaram isAsyncSafeEnvironment If true, only async-safe functions are allowed from now on.
 */
bool kscm_notifyFatalExceptionCaptured(bool isAsyncSafeEnvironment);
/** Start general exception processing.
 *
 * @oaram context Contextual information about the exception.
 */
void kscm_handleException(struct KSCrash_MonitorContext* context);
#ifdef __cplusplus
}
#endif
#endif // HDR_KSCrashMonitor_h

因为monitor是c言语完成,无法运用承继联系,那么是怎么做到一致调用敞开各个monitor的

typedef struct
{
    KSCrashMonitorType monitorType;
    KSCrashMonitorAPI* (*getAPI)(void);
} Monitor;
static Monitor g_monitors[] =
{
#if KSCRASH_HAS_MACH
    {
        .monitorType = KSCrashMonitorTypeMachException,
        .getAPI = kscm_machexception_getAPI,
    },
#endif
#if KSCRASH_HAS_SIGNAL
    {
        .monitorType = KSCrashMonitorTypeSignal,
        .getAPI = kscm_signal_getAPI,
    },
#endif
#if KSCRASH_HAS_OBJC
    {
        .monitorType = KSCrashMonitorTypeNSException,
        .getAPI = kscm_nsexception_getAPI,
    },
    {
        .monitorType = KSCrashMonitorTypeMainThreadDeadlock,
        .getAPI = kscm_deadlock_getAPI,
    },
    {
        .monitorType = KSCrashMonitorTypeZombie,
        .getAPI = kscm_zombie_getAPI,
    },
#endif
    {
        .monitorType = KSCrashMonitorTypeCPPException,
        .getAPI = kscm_cppexception_getAPI,
    },
    {
        .monitorType = KSCrashMonitorTypeUserReported,
        .getAPI = kscm_user_getAPI,
    },
    {
        .monitorType = KSCrashMonitorTypeSystem,
        .getAPI = kscm_system_getAPI,
    },
    {
        .monitorType = KSCrashMonitorTypeApplicationState,
        .getAPI = kscm_appstate_getAPI,
    },
};

KSCrashMonitor_MachException

关于反常的抓取和处理能够参照我的《APM – iOS Crash监控 原理浅析》一文,文末有链接。

依据Mach Message发送流程

  • 保存之前的反常端口
  • 创立mach_port
  • 赋予Task对port的指定权限
  • 把port作为反常处理handler
  • 创立反常处理线程
  • 停止反常监控的时分,复原为原先的exception ports
static bool installExceptionHandler()
{
    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;
    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,
                                  (int)(EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES),
                                  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_secondaryPThread,
                           &attr,
                           &handleExceptions,
                           (void*)kThreadSecondary);
    if(error != 0)
    {
        KSLOG_ERROR("pthread_create_suspended_np: %s", strerror(error));
        goto failed;
    }
    g_secondaryMachThread = pthread_mach_thread_np(g_secondaryPThread);
    ksmc_addReservedThread(g_secondaryMachThread);
    KSLOG_DEBUG("Creating primary exception thread.");
    error = pthread_create(&g_primaryPThread,
                           &attr,
                           &handleExceptions,
                           (void*)kThreadPrimary);
    if(error != 0)
    {
        KSLOG_ERROR("pthread_create: %s", strerror(error));
        goto failed;
    }
    pthread_attr_destroy(&attr);
    g_primaryMachThread = pthread_mach_thread_np(g_primaryPThread);
    ksmc_addReservedThread(g_primaryMachThread);
    KSLOG_DEBUG("Mach exception handler installed.");
    return true;
failed:
    KSLOG_DEBUG("Failed to install mach exception handler.");
    if(attributes_created)
    {
        pthread_attr_destroy(&attr);
    }
    uninstallExceptionHandler();
    return false;
}
static void uninstallExceptionHandler()
{
    KSLOG_DEBUG("Uninstalling mach exception handler.");
    // NOTE: Do not deallocate the exception port. If a secondary crash occurs
    // it will hang the process.
    restoreExceptionPorts();
    thread_t thread_self = (thread_t)ksthread_self();
    if(g_primaryPThread != 0 && g_primaryMachThread != thread_self)
    {
        KSLOG_DEBUG("Canceling primary exception thread.");
        if(g_isHandlingCrash)
        {
            thread_terminate(g_primaryMachThread);
        }
        else
        {
            pthread_cancel(g_primaryPThread);
        }
        g_primaryMachThread = 0;
        g_primaryPThread = 0;
    }
    if(g_secondaryPThread != 0 && g_secondaryMachThread != thread_self)
    {
        KSLOG_DEBUG("Canceling secondary exception thread.");
        if(g_isHandlingCrash)
        {
            thread_terminate(g_secondaryMachThread);
        }
        else
        {
            pthread_cancel(g_secondaryPThread);
        }
        g_secondaryMachThread = 0;
        g_secondaryPThread = 0;
    }
    g_exceptionPort = MACH_PORT_NULL;
    KSLOG_DEBUG("Mach exception handlers uninstalled.");
}

KSCrashMonitor_Signal

关于Unix-like中Signal反常的抓取首要有signal()和sigaction()两种办法

static bool installSignalHandler()
{
    KSLOG_DEBUG("Installing signal handler.");
#if KSCRASH_HAS_SIGNAL_STACK
    if(g_signalStack.ss_size == 0)
    {
        KSLOG_DEBUG("Allocating signal stack area.");
        g_signalStack.ss_size = SIGSTKSZ;
        g_signalStack.ss_sp = malloc(g_signalStack.ss_size);
    }
    KSLOG_DEBUG("Setting signal stack area.");
    if(sigaltstack(&g_signalStack, NULL) != 0)
    {
        KSLOG_ERROR("signalstack: %s", strerror(errno));
        goto failed;
    }
#endif
    const int* fatalSignals = kssignal_fatalSignals();
    int fatalSignalsCount = kssignal_numFatalSignals();
    if(g_previousSignalHandlers == NULL)
    {
        KSLOG_DEBUG("Allocating memory to store previous signal handlers.");
        g_previousSignalHandlers = malloc(sizeof(*g_previousSignalHandlers)
                                          * (unsigned)fatalSignalsCount);
    }
    struct sigaction action = {{0}};
    action.sa_flags = SA_SIGINFO | SA_ONSTACK;
#if KSCRASH_HOST_APPLE && defined(__LP64__)
    action.sa_flags |= SA_64REGSET;
#endif
    sigemptyset(&action.sa_mask);
    action.sa_sigaction = &handleSignal;
    for(int i = 0; i < fatalSignalsCount; i++)
    {
        KSLOG_DEBUG("Assigning handler for signal %d", fatalSignals[i]);
        if(sigaction(fatalSignals[i], &action, &g_previousSignalHandlers[i]) != 0)
        {
            char sigNameBuff[30];
            const char* sigName = kssignal_signalName(fatalSignals[i]);
            if(sigName == NULL)
            {
                snprintf(sigNameBuff, sizeof(sigNameBuff), "%d", fatalSignals[i]);
                sigName = sigNameBuff;
            }
            KSLOG_ERROR("sigaction (%s): %s", sigName, strerror(errno));
            // Try to reverse the damage
            for(i--;i >= 0; i--)
            {
                sigaction(fatalSignals[i], &g_previousSignalHandlers[i], NULL);
            }
            goto failed;
        }
    }
    KSLOG_DEBUG("Signal handlers installed.");
    return true;
failed:
    KSLOG_DEBUG("Failed to install signal handlers.");
    return false;
}
static void uninstallSignalHandler(void)
{
    KSLOG_DEBUG("Uninstalling signal handlers.");
    const int* fatalSignals = kssignal_fatalSignals();
    int fatalSignalsCount = kssignal_numFatalSignals();
    for(int i = 0; i < fatalSignalsCount; i++)
    {
        KSLOG_DEBUG("Restoring original handler for signal %d", fatalSignals[i]);
        sigaction(fatalSignals[i], &g_previousSignalHandlers[i], NULL);
    }
#if KSCRASH_HAS_SIGNAL_STACK
    g_signalStack = (stack_t){0};
#endif
    KSLOG_DEBUG("Signal handlers uninstalled.");
}

KSCrashMonitor_NSException

怎么处理存在多个NSException反常捕获的问题,先运用NSGetUncaughtExceptionHandler,保存Handler,之后在处理完成的时分,复原保存好的Handler句柄

static void handleException(NSException* exception, BOOL currentSnapshotUserReported) {
    KSLOG_DEBUG(@"Trapped exception %@", exception);
    if(g_isEnabled)
    {
        thread_act_array_t threads = NULL;
        mach_msg_type_number_t numThreads = 0;
        ksmc_suspendEnvironment(&threads, &numThreads);
        kscm_notifyFatalExceptionCaptured(false);
        KSLOG_DEBUG(@"Filling out context.");
        NSArray* addresses = [exception callStackReturnAddresses];
        NSUInteger numFrames = addresses.count;
        uintptr_t* callstack = malloc(numFrames * sizeof(*callstack));
        for(NSUInteger i = 0; i < numFrames; i++)
        {
            callstack[i] = (uintptr_t)[addresses[i] unsignedLongLongValue];
        }
        char eventID[37];
        ksid_generate(eventID);
        KSMC_NEW_CONTEXT(machineContext);
        ksmc_getContextForThread(ksthread_self(), machineContext, true);
        KSStackCursor cursor;
        kssc_initWithBacktrace(&cursor, callstack, (int)numFrames, 0);
        KSCrash_MonitorContext* crashContext = &g_monitorContext;
        memset(crashContext, 0, sizeof(*crashContext));
        crashContext->crashType = KSCrashMonitorTypeNSException;
        crashContext->eventID = eventID;
        crashContext->offendingMachineContext = machineContext;
        crashContext->registersAreValid = false;
        crashContext->NSException.name = [[exception name] UTF8String];
        crashContext->NSException.userInfo = [[NSString stringWithFormat:@"%@", exception.userInfo] UTF8String];
        crashContext->exceptionName = crashContext->NSException.name;
        crashContext->crashReason = [[exception reason] UTF8String];
        crashContext->stackCursor = &cursor;
        crashContext->currentSnapshotUserReported = currentSnapshotUserReported;
        KSLOG_DEBUG(@"Calling main crash handler.");
        kscm_handleException(crashContext);
        free(callstack);
        if (currentSnapshotUserReported) {
            ksmc_resumeEnvironment(threads, numThreads);
        }
        if (g_previousUncaughtExceptionHandler != NULL)
        {
            KSLOG_DEBUG(@"Calling original exception handler.");
            g_previousUncaughtExceptionHandler(exception);
        }
    }
}

KSCrashMonitor_CPPException

  • 设置反常处理函数
g_originalTerminateHandler = std::set_terminate(CPPExceptionTerminate);
  • 重写__cxa_throw

在反常产生时,会先进入此重写函数,应该先获取调用仓库并存储。再调用原始的__cxa_throw函数

typedef void (*cxa_throw_type)(void*, std::type_info*, void (*)(void*));
extern "C"
{
    void __cxa_throw(void* thrown_exception, std::type_info* tinfo, void (*dest)(void*)) __attribute__ ((weak));
    void __cxa_throw(void* thrown_exception, std::type_info* tinfo, void (*dest)(void*))
    {
        static cxa_throw_type orig_cxa_throw = NULL;
        if (g_cxaSwapEnabled == false)
        {
            captureStackTrace(NULL, NULL, NULL);
        }
        unlikely_if(orig_cxa_throw == NULL)
        {
            orig_cxa_throw = (cxa_throw_type) dlsym(RTLD_NEXT, "__cxa_throw");
        }
        orig_cxa_throw(thrown_exception, tinfo, dest);
        __builtin_unreachable();
    }
}
  • 反常处理函数

__cxa_throw往后执行,进入set_terminate设置的反常处理函数。判别假如检测是OC反常,则什么也不做,让OC反常机制处理。否则获取反常信息

static void CPPExceptionTerminate(void)
{
    thread_act_array_t threads = NULL;
    mach_msg_type_number_t numThreads = 0;
    ksmc_suspendEnvironment(&threads, &numThreads);
    KSLOG_DEBUG("Trapped c++ exception");
    const char* name = NULL;
    std::type_info* tinfo = __cxxabiv1::__cxa_current_exception_type();
    if(tinfo != NULL)
    {
        name = tinfo->name();
    }
    if(name == NULL || strcmp(name, "NSException") != 0)
    {
        kscm_notifyFatalExceptionCaptured(false);
        KSCrash_MonitorContext* crashContext = &g_monitorContext;
        memset(crashContext, 0, sizeof(*crashContext));
        char descriptionBuff[DESCRIPTION_BUFFER_LENGTH];
        const char* description = descriptionBuff;
        descriptionBuff[0] = 0;
        KSLOG_DEBUG("Discovering what kind of exception was thrown.");
        g_captureNextStackTrace = false;
        try
        {
            throw;
        }
        catch(std::exception& exc)
        {
            strncpy(descriptionBuff, exc.what(), sizeof(descriptionBuff));
        }
#define CATCH_VALUE(TYPE, PRINTFTYPE) \
catch(TYPE value)\
{ \
    snprintf(descriptionBuff, sizeof(descriptionBuff), "%" #PRINTFTYPE, value); \
}
        CATCH_VALUE(char,                 d)
        CATCH_VALUE(short,                d)
        CATCH_VALUE(int,                  d)
        CATCH_VALUE(long,                ld)
        CATCH_VALUE(long long,          lld)
        CATCH_VALUE(unsigned char,        u)
        CATCH_VALUE(unsigned short,       u)
        CATCH_VALUE(unsigned int,         u)
        CATCH_VALUE(unsigned long,       lu)
        CATCH_VALUE(unsigned long long, llu)
        CATCH_VALUE(float,                f)
        CATCH_VALUE(double,               f)
        CATCH_VALUE(long double,         Lf)
        CATCH_VALUE(char*,                s)
        catch(...)
        {
            description = NULL;
        }
        g_captureNextStackTrace = g_isEnabled;
        // TODO: Should this be done here? Maybe better in the exception handler?
        KSMC_NEW_CONTEXT(machineContext);
        ksmc_getContextForThread(ksthread_self(), machineContext, true);
        KSLOG_DEBUG("Filling out context.");
        crashContext->crashType = KSCrashMonitorTypeCPPException;
        crashContext->eventID = g_eventID;
        crashContext->registersAreValid = false;
        crashContext->stackCursor = &g_stackCursor;
        crashContext->CPPException.name = name;
        crashContext->exceptionName = name;
        crashContext->crashReason = description;
        crashContext->offendingMachineContext = machineContext;
        kscm_handleException(crashContext);
    }
    else
    {
        KSLOG_DEBUG("Detected NSException. Letting the current NSException handler deal with it.");
    }
    ksmc_resumeEnvironment(threads, numThreads);
    KSLOG_DEBUG("Calling original terminate handler.");
    g_originalTerminateHandler();
}

记载

在监听到对应Crash的时分,需求通过一些办法获取Crash陈述中需求的各项字段。作为记载模块,首要担任Crash Report的存取

  • 数据格局和内容

数据格局

Crash陈述

APM - iOS Crash监控 KSCrash代码解析

Header

设备,APP等相关信息

Incident Identifier: 6156848E-344E-4D9E-84E0-87AFD0D0AE7B
CrashReporter Key:   76f2fb60060d6a7f814973377cbdc866fffd521f
Hardware Model:      iPhone8,1
Process:             TouchCanvas [1052]
Path:                /private/var/containers/Bundle/Application/51346174-37EF-4F60-B72D-8DE5F01035F5/TouchCanvas.app/TouchCanvas
Identifier:          com.example.apple-samplecode.TouchCanvas
Version:             1 (3.0)
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           com.example.apple-samplecode.TouchCanvas [1806]
Date/Time:           2020-03-27 18:06:51.4969 -0700
Launch Time:         2020-03-27 18:06:31.7593 -0700
OS Version:          iPhone OS 13.3.1 (17D50)

Exception information

反常信息

Exception Type:  EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x0000000102afb3d0

Diagnostic messages

对反常解析后的信息

  • Crash比如
Application Specific Information:
BUG IN CLIENT OF LIBDISPATCH: dispatch_sync called on queue already owned by current thread
  • WatchDog比如
Termination Description: SPRINGBOARD,
    scene-create watchdog transgression: application<com.example.MyCoolApp>:667
    exhausted real (wall clock) time allowance of 19.97 seconds 
  • 内存拜访比如
VM Region Info: 0 is not in any region.  Bytes before following region: 4307009536
      REGION TYPE                      START - END             [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
      UNUSED SPACE AT START
--->  
      __TEXT                 0000000100b7c000-0000000100b84000 [   32K] r-x/r-x SM=COW  ...pp/MyGreatApp

Backtraces

Crash线程,主线程,各个线程的调用栈信息

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   TouchCanvas                       0x0000000102afb3d0 CanvasView.updateEstimatedPropertiesForTouches(_:) + 62416 (CanvasView.swift:231)
1   TouchCanvas                       0x0000000102afb3d0 CanvasView.updateEstimatedPropertiesForTouches(_:) + 62416 (CanvasView.swift:231)
2   TouchCanvas                       0x0000000102af7d10 ViewController.touchesMoved(_:with:) + 48400 (<compiler-generated>:0)
3   TouchCanvas                       0x0000000102af80b8 @objc ViewController.touchesMoved(_:with:) + 49336 (<compiler-generated>:0)
4   UIKitCore                         0x00000001ba9d8da4 forwardTouchMethod + 328
5   UIKitCore                         0x00000001ba9d8e40 -[UIResponder touchesMoved:withEvent:] + 60
6   UIKitCore                         0x00000001ba9d8da4 forwardTouchMethod + 328
7   UIKitCore                         0x00000001ba9d8e40 -[UIResponder touchesMoved:withEvent:] + 60
8   UIKitCore                         0x00000001ba9e6ea4 -[UIWindow _sendTouchesForEvent:] + 1896
9   UIKitCore                         0x00000001ba9e8390 -[UIWindow sendEvent:] + 3352
10  UIKitCore                         0x00000001ba9c4a9c -[UIApplication sendEvent:] + 344
11  UIKitCore                         0x00000001baa3cc20 __dispatchPreprocessedEventFromEventQueue + 5880
12  UIKitCore                         0x00000001baa3f17c __handleEventQueueInternal + 4924
13  UIKitCore                         0x00000001baa37ff0 __handleHIDEventFetcherDrain + 108
14  CoreFoundation                    0x00000001b68a4a00 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
15  CoreFoundation                    0x00000001b68a4958 __CFRunLoopDoSource0 + 80
16  CoreFoundation                    0x00000001b68a40f0 __CFRunLoopDoSources0 + 180
17  CoreFoundation                    0x00000001b689f23c __CFRunLoopRun + 1080
18  CoreFoundation                    0x00000001b689eadc CFRunLoopRunSpecific + 464
19  GraphicsServices                  0x00000001c083f328 GSEventRunModal + 104
20  UIKitCore                         0x00000001ba9ac63c UIApplicationMain + 1936
21  TouchCanvas                       0x0000000102af16dc main + 22236 (AppDelegate.swift:12)
22  libdyld.dylib                     0x00000001b6728360 start + 4
Thread 1:
0   libsystem_pthread.dylib           0x00000001b6645758 start_wqthread + 0
Thread 2:
0   libsystem_pthread.dylib           0x00000001b6645758 start_wqthread + 0
...

Crashed thread state

寄存器信息,29个通用寄存器,以及Frame Pointer,Link Register,Stack Pointer,Program Counter,Current Program Status Register寄存器信息

Thread 0 crashed with ARM Thread State (64-bit):
    x0: 0x0000000000000001   x1: 0x0000000000000000   x2: 0x0000000000000000   x3: 0x000000000000000f
    x4: 0x00000000000001c2   x5: 0x000000010327f6c0   x6: 0x000000010327f724   x7: 0x0000000000000120
    x8: 0x0000000000000001   x9: 0x0000000000000001  x10: 0x0000000000000001  x11: 0x0000000000000000
   x12: 0x00000001038612b0  x13: 0x000005a102b075a7  x14: 0x0000000000000100  x15: 0x0000010000000000
   x16: 0x00000001c3e6c630  x17: 0x00000001bae4bbf8  x18: 0x0000000000000000  x19: 0x0000000282c14280
   x20: 0x00000001fe64a3e0  x21: 0x4000000281f1df10  x22: 0x0000000000000001  x23: 0x0000000000000000
   x24: 0x0000000000000000  x25: 0x0000000282c14280  x26: 0x0000000103203140  x27: 0x00000001bacf4b7c
   x28: 0x00000001fe5ded08   fp: 0x000000016d311310   lr: 0x0000000102afb3d0
    sp: 0x000000016d311200   pc: 0x0000000102afb3d0 cpsr: 0x60000000
   esr: 0xf2000001  Address size fault

Binary images list

可执行文件,各个动态库,体系动态库分别为一个Image

Binary Images:0x102aec000 - 0x102b03fff TouchCanvas arm64  <fe7745ae12db30fa886c8baa1980437a> /var/containers/Bundle/Application/51346174-37EF-4F60-B72D-8DE5F01035F5/TouchCanvas.app/TouchCanvas...

KSCrash_MonitorContext数据结构

KSCrash_MonitorContext的数据结构用于在monitorManager与各个monitor之间传递,其中的字段与最终生存的Crash Report是一一对应的联系

typedef struct KSCrash_MonitorContext
{
    /** Unique identifier for this event. */
    const char* eventID;
    /**
     If true, so reported user exception will have the current snapshot.
     */
    bool currentSnapshotUserReported;
    /** If true, the environment has crashed hard, and only async-safe
     *  functions should be used.
     */
    bool requiresAsyncSafety;
    /** If true, the crash handling system is currently handling a crash.
     * When false, all values below this field are considered invalid.
     */
    bool handlingCrash;
    /** If true, a second crash occurred while handling a crash. */
    bool crashedDuringCrashHandling;
    /** If true, the registers contain valid information about the crash. */
    bool registersAreValid;
    /** True if the crash system has detected a stack overflow. */
    bool isStackOverflow;
    /** The machine context that generated the event. */
    struct KSMachineContext* offendingMachineContext;
    /** Address that caused the fault. */
    uintptr_t faultAddress;
    /** The type of crash that occurred.
     * This determines which other fields are valid. */
    KSCrashMonitorType crashType;
    /** The name of the exception that caused the crash, if any. */
    const char* exceptionName;
    /** Short description of why the crash occurred. */
    const char* crashReason;
    /** The stack cursor for the trace leading up to the crash.
     *  Note: Actual type is KSStackCursor*
     */
    void* stackCursor;
    struct
    {
        /** The mach exception type. */
        int type;
        /** The mach exception code. */
        int64_t code;
        /** The mach exception subcode. */
        int64_t subcode;
    } mach;
    struct
    {
        /** The exception name. */
        const char* name;
        /** The exception userInfo. */
        const char* userInfo;
    } NSException;
    struct
    {
        /** The exception name. */
        const char* name;
    } CPPException;
    struct
    {
        /** User context information. */
        const void* userContext;
        int signum;
        int sigcode;
    } signal;
    struct
    {
        /** The exception name. */
        const char* name;
        /** The language the exception occured in. */
        const char* language;
        /** The line of code where the exception occurred. Can be NULL. */
        const char* lineOfCode;
        /** The user-supplied JSON encoded stack trace. */
        const char* customStackTrace;
    } userException;
    struct
    {
        /** Total active time elapsed since the last crash. */
        double activeDurationSinceLastCrash;
        /** Total time backgrounded elapsed since the last crash. */
        double backgroundDurationSinceLastCrash;
        /** Number of app launches since the last crash. */
        int launchesSinceLastCrash;
        /** Number of sessions (launch, resume from suspend) since last crash. */
        int sessionsSinceLastCrash;
        /** Total active time elapsed since launch. */
        double activeDurationSinceLaunch;
        /** Total time backgrounded elapsed since launch. */
        double backgroundDurationSinceLaunch;
        /** Number of sessions (launch, resume from suspend) since app launch. */
        int sessionsSinceLaunch;
        /** If true, the application crashed on the previous launch. */
        bool crashedLastLaunch;
        /** If true, the application crashed on this launch. */
        bool crashedThisLaunch;
        /** Timestamp for when the app state was last changed (active<->inactive,
         * background<->foreground) */
        double appStateTransitionTime;
        /** If true, the application is currently active. */
        bool applicationIsActive;
        /** If true, the application is currently in the foreground. */
        bool applicationIsInForeground;
    } AppState;
    /* Misc system information */
    struct
    {
        const char* systemName;
        const char* systemVersion;
        const char* machine;
        const char* model;
        const char* kernelVersion;
        const char* osVersion;
        bool isJailbroken;
        const char* bootTime;
        const char* appStartTime;
        const char* executablePath;
        const char* executableName;
        const char* bundleID;
        const char* bundleName;
        const char* bundleVersion;
        const char* bundleShortVersion;
        const char* appID;
        const char* cpuArchitecture;
        int cpuType;
        int cpuSubType;
        int binaryCPUType;
        int binaryCPUSubType;
        const char* timezone;
        const char* processName;
        int processID;
        int parentProcessID;
        const char* deviceAppHash;
        const char* buildType;
        uint64_t storageSize;
        uint64_t memorySize;
        uint64_t freeMemory;
        uint64_t usableMemory;
    } System;
    struct
    {
        /** Address of the last deallocated exception. */
        uintptr_t address;
        /** Name of the last deallocated exception. */
        const char* name;
        /** Reason field from the last deallocated exception. */
        const char* reason;
    } ZombieException;
    /** Full path to the console log, if any. */
    const char* consoleLogPath;
} KSCrash_MonitorContext;

KSCrash发动的时分,设置了crash产生时的回调

KSCrashMonitorType kscrash_install(const char* appName, const char* const installPath) {
    //Codes
    kscm_setEventCallback(onCrash);
    //Codes
}

Crash产生时,回调出来的数据类型是KSCrash_MonitorContext

  • 依据装备判别是否需求添加控制台日志到陈述中
  • 判别是否在Crash处理中产生的二次Crash
  • 写入完成,回调给外部
// ============================================================================
#pragma mark - Callbacks -
// ============================================================================
/** Called when a crash occurs.
 *
 * This function gets passed as a callback to a crash handler.
 */
static void onCrash(struct KSCrash_MonitorContext* monitorContext)
{
    if (monitorContext->currentSnapshotUserReported == false) {
        KSLOG_DEBUG("Updating application state to note crash.");
        kscrashstate_notifyAppCrash();
    }
    monitorContext->consoleLogPath = g_shouldAddConsoleLogToReport ? g_consoleLogPath : NULL;
    if(monitorContext->crashedDuringCrashHandling)
    {
        kscrashreport_writeRecrashReport(monitorContext, g_lastCrashReportFilePath);
    }
    else
    {
        char crashReportFilePath[KSFU_MAX_PATH_LENGTH];
        int64_t reportID = kscrs_getNextCrashReport(crashReportFilePath);
        strncpy(g_lastCrashReportFilePath, crashReportFilePath, sizeof(g_lastCrashReportFilePath));
        kscrashreport_writeStandardReport(monitorContext, crashReportFilePath);
        if(g_reportWrittenCallback)
        {
            g_reportWrittenCallback(reportID);
        }
    }
}

KSCrashReportStore

  • 判别对应的buffer是否存在,获取对应的buffer
int64_t kscrs_getNextCrashReport(char* crashReportPathBuffer)
{
    int64_t nextID = getNextUniqueID();
    if(crashReportPathBuffer)
    {
        getCrashReportPathByID(nextID, crashReportPathBuffer);
    }
    return nextID;
}

KSCrashReport

创立writer,往buffer中写入对应字段的数据

void kscrashreport_writeStandardReport(const KSCrash_MonitorContext* const monitorContext, const char* const path)
{
    KSLOG_INFO("Writing crash report to %s", path);
    char writeBuffer[1024];
    KSBufferedWriter bufferedWriter;
    if(!ksfu_openBufferedWriter(&bufferedWriter, path, writeBuffer, sizeof(writeBuffer)))
    {
        return;
    }
    ksccd_freeze();
    KSJSONEncodeContext jsonContext;
    jsonContext.userData = &bufferedWriter;
    KSCrashReportWriter concreteWriter;
    KSCrashReportWriter* writer = &concreteWriter;
    prepareReportWriter(writer, &jsonContext);
    ksjson_beginEncode(getJsonContext(writer), true, addJSONData, &bufferedWriter);
    writer->beginObject(writer, KSCrashField_Report);
    {
        writeReportInfo(writer,
                        KSCrashField_Report,
                        KSCrashReportType_Standard,
                        monitorContext->eventID,
                        monitorContext->System.processName);
        ksfu_flushBufferedWriter(&bufferedWriter);
        writeBinaryImages(writer, KSCrashField_BinaryImages);
        ksfu_flushBufferedWriter(&bufferedWriter);
        writeProcessState(writer, KSCrashField_ProcessState, monitorContext);
        ksfu_flushBufferedWriter(&bufferedWriter);
        writeSystemInfo(writer, KSCrashField_System, monitorContext);
        ksfu_flushBufferedWriter(&bufferedWriter);
        writer->beginObject(writer, KSCrashField_Crash);
        {
            writeError(writer, KSCrashField_Error, monitorContext);
            ksfu_flushBufferedWriter(&bufferedWriter);
            writeAllThreads(writer,
                            KSCrashField_Threads,
                            monitorContext,
                            g_introspectionRules.enabled);
            ksfu_flushBufferedWriter(&bufferedWriter);
        }
        writer->endContainer(writer);
        if(g_userInfoJSON != NULL)
        {
            addJSONElement(writer, KSCrashField_User, g_userInfoJSON, false);
            ksfu_flushBufferedWriter(&bufferedWriter);
        }
        else
        {
            writer->beginObject(writer, KSCrashField_User);
        }
        if(g_userSectionWriteCallback != NULL)
        {
            ksfu_flushBufferedWriter(&bufferedWriter);
            if (monitorContext->currentSnapshotUserReported == false) {
                g_userSectionWriteCallback(writer);
            }
        }
        writer->endContainer(writer);
        ksfu_flushBufferedWriter(&bufferedWriter);
        writeDebugInfo(writer, KSCrashField_Debug, monitorContext);
    }
    writer->endContainer(writer);
    ksjson_endEncode(getJsonContext(writer));
    ksfu_closeBufferedWriter(&bufferedWriter);
    ksccd_unfreeze();
}

除此之外KSCrashReport还担任多个类型数据的写入

  • Backtrace
  • Stack
  • Registers
  • Thread-Specific
  • Global Report Data

KSCrashReportWriter

Writer中封装了对应字段的写入办法

/**
 * Encapsulates report writing functionality.
 */
typedef struct KSCrashReportWriter
{
    /** Add a boolean element to the report.
     *
     * @param writer This writer.
     *
     * @param name The name to give this element.
     *
     * @param value The value to add.
     */
    void (*addBooleanElement)(const struct KSCrashReportWriter* writer,
                              const char* name,
                              bool value);
    /** Add a floating point element to the report.
     *
     * @param writer This writer.
     *
     * @param name The name to give this element.
     *
     * @param value The value to add.
     */
    void (*addFloatingPointElement)(const struct KSCrashReportWriter* writer,
                                    const char* name,
                                    double value);
    /** Add an integer element to the report.
     *
     * @param writer This writer.
     *
     * @param name The name to give this element.
     *
     * @param value The value to add.
     */
    void (*addIntegerElement)(const struct KSCrashReportWriter* writer,
                              const char* name,
                              int64_t value);
    /** Add an unsigned integer element to the report.
     *
     * @param writer This writer.
     *
     * @param name The name to give this element.
     *
     * @param value The value to add.
     */
    void (*addUIntegerElement)(const struct KSCrashReportWriter* writer,
                               const char* name,
                               uint64_t value);
    /** Add a string element to the report.
     *
     * @param writer This writer.
     *
     * @param name The name to give this element.
     *
     * @param value The value to add.
     */
    void (*addStringElement)(const struct KSCrashReportWriter* writer,
                             const char* name,
                             const char* value);
    /** Add a string element from a text file to the report.
     *
     * @param writer This writer.
     *
     * @param name The name to give this element.
     *
     * @param filePath The path to the file containing the value to add.
     */
    void (*addTextFileElement)(const struct KSCrashReportWriter* writer,
                               const char* name,
                               const char* filePath);
    /** Add an array of string elements representing lines from a text file to the report.
     *
     * @param writer This writer.
     *
     * @param name The name to give this element.
     *
     * @param filePath The path to the file containing the value to add.
     */
    void (*addTextFileLinesElement)(const struct KSCrashReportWriter* writer,
                                    const char* name,
                                    const char* filePath);
    /** Add a JSON element from a text file to the report.
     *
     * @param writer This writer.
     *
     * @param name The name to give this element.
     *
     * @param filePath The path to the file containing the value to add.
     *
     * @param closeLastContainer If false, do not close the last container.
     */
    void (*addJSONFileElement)(const struct KSCrashReportWriter* writer,
                               const char* name,
                               const char* filePath,
                               const bool closeLastContainer);
    /** Add a hex encoded data element to the report.
     *
     * @param writer This writer.
     *
     * @param name The name to give this element.
     *
     * @param value A pointer to the binary data.
     *
     * @paramn length The length of the data.
     */
    void (*addDataElement)(const struct KSCrashReportWriter* writer,
                           const char* name,
                           const char* value,
                           const int length);
    /** Begin writing a hex encoded data element to the report.
     *
     * @param writer This writer.
     *
     * @param name The name to give this element.
     */
    void (*beginDataElement)(const struct KSCrashReportWriter* writer,
                             const char* name);
    /** Append hex encoded data to the current data element in the report.
     *
     * @param writer This writer.
     *
     * @param value A pointer to the binary data.
     *
     * @paramn length The length of the data.
     */
    void (*appendDataElement)(const struct KSCrashReportWriter* writer,
                              const char* value,
                              const int length);
    /** Complete writing a hex encoded data element to the report.
     *
     * @param writer This writer.
     */
    void (*endDataElement)(const struct KSCrashReportWriter* writer);
    /** Add a UUID element to the report.
     *
     * @param writer This writer.
     *
     * @param name The name to give this element.
     *
     * @param value A pointer to the binary UUID data.
     */
    void (*addUUIDElement)(const struct KSCrashReportWriter* writer,
                           const char* name,
                           const unsigned char* value);
    /** Add a preformatted JSON element to the report.
     *
     * @param writer This writer.
     *
     * @param name The name to give this element.
     *
     * @param value A pointer to the JSON data.
     */
    void (*addJSONElement)(const struct KSCrashReportWriter* writer,
                           const char* name,
                           const char* jsonElement,
                           bool closeLastContainer);
    /** Begin a new object container.
     *
     * @param writer This writer.
     *
     * @param name The name to give this element.
     */
    void (*beginObject)(const struct KSCrashReportWriter* writer,
                        const char* name);
    /** Begin a new array container.
     *
     * @param writer This writer.
     *
     * @param name The name to give this element.
     */
    void (*beginArray)(const struct KSCrashReportWriter* writer,
                       const char* name);
    /** Leave the current container, returning to the next higher level
     *  container.
     *
     * @param writer This writer.
     */
    void (*endContainer)(const struct KSCrashReportWriter* writer);
    /** Internal contextual data for the writer */
    void* context;
} KSCrashReportWriter;

在KSCrash中供给了部分操作Crash Report的接口,包括了取和删除操作

@interface KSCrash : NSObject
/** Get all unsent report IDs.
 *
 * @return An array with report IDs.
 */
- (NSArray*) reportIDs;
/** Get report.
 *
 * @param reportID An ID of report.
 *
 * @return A dictionary with report fields. See KSCrashReportFields.h for available fields.
 */
- (NSDictionary*) reportWithID:(NSNumber*) reportID;
/** Delete all unsent reports.
 */
- (void) deleteAllReports;
/** Delete report.
 *
 * @param reportID An ID of report to delete.
 */
- (void) deleteReportWithID:(NSNumber*) reportID;
@end

归因

KSCrashDoctor

用于获取苹果崩溃陈述中Diagnostic messages段的内容

- (NSString*) diagnoseCrash:(NSDictionary*) report
{
    @try
    {
        NSString* lastFunctionName = [[self lastInAppStackEntry:report] objectForKey:@KSCrashField_SymbolName];
        NSDictionary* crashedThreadReport = [self crashedThreadReport:report];
        NSDictionary* errorReport = [self errorReport:report];
        if([self isDeadlock:report])
        {
            return [NSString stringWithFormat:@"Main thread deadlocked in %@", lastFunctionName];
        }
        if([self isStackOverflow:crashedThreadReport])
        {
            return [NSString stringWithFormat:@"Stack overflow in %@", lastFunctionName];
        }
        NSString* crashType = [errorReport objectForKey:@KSCrashField_Type];
        if([crashType isEqualToString:@KSCrashExcType_NSException])
        {
            NSDictionary* exception = [errorReport objectForKey:@KSCrashField_NSException];
            NSString* name = [exception objectForKey:@KSCrashField_Name];
            NSString* reason = [exception objectForKey:@KSCrashField_Reason]? [exception objectForKey:@KSCrashField_Reason]:[errorReport objectForKey:@KSCrashField_Reason];
            return [self appendOriginatingCall:[NSString stringWithFormat:@"Application threw exception %@: %@",
                                                name, reason]
                                      callName:lastFunctionName];
        }
        if([self isMemoryCorruption:report])
        {
            return @"Rogue memory write has corrupted memory.";
        }
        if([self isMathError:errorReport])
        {
            return [self appendOriginatingCall:[NSString stringWithFormat:@"Math error (usually caused from division by 0)."]
                                      callName:lastFunctionName];
        }
        KSCrashDoctorFunctionCall* functionCall = [self lastFunctionCall:report];
        NSString* zombieCall = [self zombieCall:functionCall];
        if(zombieCall != nil)
        {
            return [self appendOriginatingCall:[NSString stringWithFormat:@"Possible zombie in call: %@", zombieCall]
                                      callName:lastFunctionName];
        }
        if([self isInvalidAddress:errorReport])
        {
            uintptr_t address = (uintptr_t)[[errorReport objectForKey:@KSCrashField_Address] unsignedLongLongValue];
            if(address == 0)
            {
                return [self appendOriginatingCall:@"Attempted to dereference null pointer."
                                          callName:lastFunctionName];
            }
            return [self appendOriginatingCall:[NSString stringWithFormat:@"Attempted to dereference garbage pointer %p.", (void*)address]
                                      callName:lastFunctionName];
        }
        return nil;
    }
    @catch (NSException* e)
    {
        NSArray* symbols = [e callStackSymbols];
        if(symbols)
        {
            return [NSString stringWithFormat:@"No diagnosis due to exception %@:\n%@\nPlease file a bug report to the KSCrash project.", e, symbols];
        }
        return [NSString stringWithFormat:@"No diagnosis due to exception %@\nPlease file a bug report to the KSCrash project.", e];
    }
}

因为Diagnostic messages的内容是依据元数据上剖析得到的,所以能够在数据收集完成之后的某个阶段调用KSCrashDoctor剖析得到对应字段的内容。此处是在获取report的时分才调用对应的diagnostic

- (NSDictionary*) reportWithIntID:(int64_t) reportID
{
    NSData* jsonData = [self loadCrashReportJSONWithID:reportID];
    if(jsonData == nil)
    {
        return nil;
    }
    NSError* error = nil;
    NSMutableDictionary* crashReport = [KSJSONCodec decode:jsonData
                                                   options:KSJSONDecodeOptionIgnoreNullInArray |
                                                           KSJSONDecodeOptionIgnoreNullInObject |
                                                           KSJSONDecodeOptionKeepPartialObject
                                                     error:&error];
    if(error != nil)
    {
        KSLOG_ERROR(@"Encountered error loading crash report %" PRIx64 ": %@", reportID, error);
    }
    if(crashReport == nil)
    {
        KSLOG_ERROR(@"Could not load crash report");
        return nil;
    }
    [self doctorReport:crashReport];
    return crashReport;
}

上报

获得了Crash Report之后,咱们需求从APP中上签到对应服务器才方便做后续的操作。

  • 过滤

  • 上报装备

  • 网络请求

KSCrash中供给了sendAllReportsWithCompletion办法用于上报一切reports

@interface KSCrash : NSObject
/** Send all outstanding crash reports to the current sink.
 * It will only attempt to send the most recent 5 reports. All others will be
 * deleted. Once the reports are successfully sent to the server, they may be
 * deleted locally, depending on the property "deleteAfterSendAll".
 *
 * Note: property "sink" MUST be set or else this method will call onCompletion
 *       with an error.
 *
 * @param onCompletion Called when sending is complete (nil = ignore).
 */
- (void) sendAllReportsWithCompletion:(KSCrashReportFilterCompletion) onCompletion;
@end

可是从源码上看,必须要有设置sink的条件,这边的sink是由一开始初始化的KSCrashInstallation的子类决议的,不同的子类对sink是不同的完成

- (void) sendReports:(NSArray*) reports onCompletion:(KSCrashReportFilterCompletion) onCompletion
{
    if([reports count] == 0)
    {
        kscrash_callCompletion(onCompletion, reports, YES, nil);
        return;
    }
    if(self.sink == nil)
    {
        kscrash_callCompletion(onCompletion, reports, NO,
                                 [NSError errorWithDomain:[[self class] description]
                                                     code:0
                                              description:@"No sink set. Crash reports not sent."]);
        return;
    }
    [self.sink filterReports:reports
                onCompletion:^(NSArray* filteredReports, BOOL completed, NSError* error)
     {
         kscrash_callCompletion(onCompletion, filteredReports, completed, error);
     }];
}

KSCrashInstallation中也对外供给了sendAllReportsWithCompletion

/**
 * Crash system installation which handles backend-specific details.
 *
 * Only one installation can be installed at a time.
 *
 * This is an abstract class.
 */
@interface KSCrashInstallation : NSObject
/** C Function to call during a crash report to give the callee an opportunity to
 * add to the report. NULL = ignore.
 *
 * WARNING: Only call async-safe functions from this function! DO NOT call
 * Objective-C methods!!!
 */
@property(atomic,readwrite,assign) KSReportWriteCallback onCrash;
/** Install this installation. Call this instead of -[KSCrash install] to install
 * with everything needed for your particular backend.
 */
- (void) install;
/** Convenience method to call -[KSCrash sendAllReportsWithCompletion:].
 * This method will set the KSCrash sink and then send all outstanding reports.
 *
 * Note: Pay special attention to KSCrash's "deleteBehaviorAfterSendAll" property.
 *
 * @param onCompletion Called when sending is complete (nil = ignore).
 */
- (void) sendAllReportsWithCompletion:(KSCrashReportFilterCompletion) onCompletion;
/** Add a filter that gets executed before all normal filters.
 * Prepended filters will be executed in the order in which they were added.
 *
 * @param filter the filter to prepend.
 */
- (void) addPreFilter:(id<KSCrashReportFilter>) filter;
@end

在sendAllReportsWithCompletion中设置了sink,此时不同的KSCrashInstallation的子类在[self sink]的时分调用了自身的sink的完成办法

- (void) sendAllReportsWithCompletion:(KSCrashReportFilterCompletion) onCompletion
{
    NSError* error = [self validateProperties];
    if(error != nil)
    {
        if(onCompletion != nil)
        {
            onCompletion(nil, NO, error);
        }
        return;
    }
    id<KSCrashReportFilter> sink = [self sink];
    if(sink == nil)
    {
        onCompletion(nil, NO, [NSError errorWithDomain:[[self class] description]
                                                  code:0
                                           description:@"Sink was nil (subclasses must implement method "sink")"]);
        return;
    }
    sink = [KSCrashReportFilterPipeline filterWithFilters:self.prependedFilters, sink, nil];
    KSCrash* handler = [KSCrash sharedInstance];
    handler.sink = sink;
    [handler sendAllReportsWithCompletion:onCompletion];
}

sink是契合KSCrashReportFilter协议的目标

/** Callback for filter operations.
 *
 * @param filteredReports The filtered reports (may be incomplete if "completed"
 *                        is false).
 * @param completed True if filtering completed.
 *                  Can be false due to a non-erroneous condition (such as a
 *                  user cancelling the operation).
 * @param error Non-nil if an error occurred.
 */
typedef void(^KSCrashReportFilterCompletion)(NSArray* filteredReports, BOOL completed, NSError* error);
/**
 * A filter receives a set of reports, possibly transforms them, and then
 * calls a completion method.
 */
@protocol KSCrashReportFilter <NSObject>
/** Filter the specified reports.
 *
 * @param reports The reports to process.
 * @param onCompletion Block to call when processing is complete.
 */
- (void) filterReports:(NSArray*) reports
          onCompletion:(KSCrashReportFilterCompletion) onCompletion;
@end

KSCrashReportFilterPipeline中对filters数组进行了复合设置

+ (KSCrashReportFilterPipeline*) filterWithFilters:(id) firstFilter, ...
{
    ksva_list_to_nsarray(firstFilter, filters);
    return [[self alloc] initWithFiltersArray:filters];
}

引证

KSCrash Github

Examining the fields in a crash report

APM – iOS Crash监控 原理浅析