简介
首要功用
- 支撑离线符号化的设备上符号化(关于部分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(例如来自脚本言语)
原理
结构
- 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监控的发动尽量放在最早的阶段
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及设备的辅助信息
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陈述
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监控 原理浅析