You need to enable JavaScript to run this app.
最新活动
产品
解决方案
定价
合作与生态
支持与服务
开发者
了解我们
导航

崩溃监控

更新时间:2023.01.31 19:27:15

parfait内部集成了Google Crashpad作为PC跨平台的crash收集工具,并基于crashpad,拓展开发了windows veh、post handler等等功能。

接入要求
  1. SDK最好不要接,宿主接即可。
  2. 初始化Parfait SDK。持有初始化成功的parfait_wrapper_ptr指针。
  3. 提供一个有权限操作的路径,用于存储崩溃报告。
初始化

macOS:

  • 主进程:

    调用InitCrashServer方法初始化主进程的崩溃监控,dump_dir为存放崩溃报告的绝对路径,同步返回Crashpad初始化结果。
/**
 * 仅Mac/Linux可用
 * 初始化Crashpad,并为调用进程注册Crash监听
 * 调用进程的子进程会自动注册Crash监听,不需要调用InitCrashClient方法
 * @param  dump_dir crash文件存储路径,绝对路径,最后以‘/’结尾
 * @return  Crashpad是否初始化成功
 */
 bool ParfaitWrapperBase::InitCrashServer(const char* dump_dir);
 
/**
 * 仅Mac/Linux可用
 * 在parfait初始化前初始化Crashpad,参数和上面一致
 * 初始化parfait后仍需要调用上述的InitCrashServer,才能上传Crash文件
 * ⚠️应用生命周期内,Crashpad只会初始化一次,参数以第一次初始化为准
 */
 static bool ParfaitWrapperBase::InitCrashServerEarly(const char* dump_dir); 

示例代码: 二选一即可
//场景1(推荐): 主进程在初始化parfait sdk后注册崩溃监控
bool init_res = parfait_wrapper_ptr->InitCrashServer("your_absolute_path/");//传入绝对路径

//场景2: 适用于特殊场景,主进程需要在初始化parfait sdk之前就注册崩溃监控
bool init_res = parfait::ParfaitWrapperBase::InitCrashServerEarly("your_absolute_path/");
parfait sdk 初始化代码
//仅提醒parfait上传Crash文件,dmp_path务必保持和InitCrashServerEarly保持一致
parfait_wrapper_ptr->InitCrashServer("your_absolute_path/");
  • 子进程

    • 同一进程组:主进程初始化崩溃监控后,该进程的所有子进程会自动注册崩溃监控,不需要再进行别的操作。请注意主进程必须在拉起子进程前初始化崩溃监控!
    • 不同进程组:需要主动为子进程重新注册崩溃监控,流程和主进程一样。
  • 沙盒应用:

沙盒应用需要额外操作:

业务需要配置:

  1. 选定一个identifier。格式为xxxx,中间可用.作分隔,如xxx.xxxx。只能是字母组合,identifier不要太长。下文举例为"macdemo"

  2. 主应用配置entitlements:
    需要新增两个用于支持bootstrap服务的key:

  • com.apple.security.temporary-exception.mach-lookup.global-name
  • com.apple.security.temporary-exception.mach-register.global-name

类型是String, Value是com.bytedance.parfait.child_port_handshake.$identifier


这里key的Value都是com.bytedance.parfait.child_port_handshake.macdemo

  1. parfait_crash_handler二进制签名:

    AppStore要求应用内所有的macho文件都必须开启沙箱才能过审,所以我们需要为parfait_crash_handler二进制签名。parfait_crash_handler二进制位于Parfait.framework/Versions/A/Resources目录下。
    命令如下:

    codesign --force --sign "$YOUR_INDENTITY" --entitlements "$PATH/parfait_crash_handler.entitlements" "$PATH/parfait_crash_handler"
parfait_crash_handler.entitlements.zip
1.03KB

parfait_crash_handler开启sandbox, 并继承主应用的权限。

验证parfait_crash_handler签名是否成功:codesign -dvvv --entitlements :- $PATH/parfait_crash_handler

  1. 修改代码

在初始化崩溃监控之前, 调用SetCrashServerIdentifierOnMac注入identifier信息。

#define CRASH_FILE_DIR "crash_databse"

std::string homeDir = getenv("HOME");
homeDir.append(CRASH_FILE_DIR);
parfait::ParfaitWrapperBase::SetCrashServerIdentifierOnMac("macdemo");
bool res = parfait_wrapper_ptr->InitCrashServer(homeDir.c_str());
if (res)
    printf("Init Crashpad success\n");
else
    printf("Init Crashpad failed\n");

验证:

  1. InitCrashServer API 返回true / 打开parfait debug log,控制台会输出Crashpad init success!

  2. 非调试模式下,发生崩溃后崩溃会立即上报到APMPlus平台。

过审说明:


因为使用了Temporary Exception Entitlements, 提审时需要说明使用原因。 此权限是为了注册崩溃监控,崩溃监控用于收集程序运行情况。

Windows:

  • 主进程

    1. parfait_crash_handler.exe存放至项目空间下(parfait_crash_handler.exe在产物包中)
    2. 首先需要实现CrashServerInitCallback回调方法。callback会返回初始化结果和子进程注册崩溃监控所需的值
    3. 在主进程的主线程调用InitCrashServerOnWin初始化Crash模块,为主进程注册崩溃监控。Windows下默认异步初始化,1.1.3版本后支持同步初始化。
/**
 * 仅Windows可用
 * ⚠️注意:必须在主进程的主线程中调用!!
 * 异步初始化Crashpad,并为调用进程注册Crash监听
 * @param  handler_path crashpad_handler.exe的绝对路径,最后以crashpad_handler.exe结尾
 * @param  dump_dir crash文件存储路径,绝对路径,最后以‘\\’结尾
 * @param  callback 返回异步初始化Crashpad的结果和其他进程调用InitCrashClientOnWin注册崩溃监听需要的ipc_pipe
 */
void ParfaitWrapperBase::InitCrashServerOnWin(const char* handler_path, const char* dump_dir, CrashServerInitCallback callback);

/**
 * 仅Windows可用
 * 在parfait初始化前初始化Crashpad,并为调用进程注册Crash监听
 * ⚠️注意:必须在主进程的主线程中调用!!Crashpad只会初始化一次,参数以第一次初始化为准!!
 * ⚠️注意:初始化parfait后仍需要调用上述的InitCrashServerOnWin,才能上传Crash文件
 * @param  handler_path crashpad_handler.exe的路径,最后以crashpad_handler.exe结尾
 * @param  dump_dir crash文件存储路径,最后以‘\\’结尾
 * @param  async_start 异步初始化, true的话callback返回异步初始化结果,本方法返回false,false该方法同步返回crashpad初始化结果,
 * @param  callback 返回异步初始化Crashpad结果和其他进程调用InitCrashClientOnWin注册崩溃监听需要的ipc_pipe
 */
static bool ParfaitWrapperBase::InitCrashServerOnWinEarly(const char* handler_path, const char* dump_dir, bool async_start, CrashServerInitCallback callback); 

示例代码: 二选一即可
//场景1(推荐): 主进程可以在初始化parfait sdk后异步注册崩溃监控
parfait sdk 初始化代码
parfait_wrapper_ptr->InitCrashServerOnWin("xxxxxxx\\crashpad_handler.exe", "xxxxX\\crashData\\", &getCrashpadInitRes);

//场景2: 适用于特殊场景,主进程需要在初始化parfait sdk之前就同步注册崩溃监控
bool init_res = parfait::ParfaitWrapperBase::InitCrashServerOnWinEarly("xxxxxxx\\parfait_crash_handler.exe", "xxxxx\\crashData\\", false, &getCrashpadInitRes);
parfait sdk 初始化代码
//仅提醒parfait上传Crash文件,dmp_path务必保持和InitCrashServerEarly保持一致
parfait_wrapper_ptr->InitCrashServerOnWin("xxxxxxx\\crashpad_handler.exe", "xxxxX\\crashData\\", nullptr);
  • 子进程

拿到回调后返回的ipc_pipe值后,可为其他进程注册崩溃监控。下面是在其他进程中的示例代码:

/**
 * 仅Windows可用
 * 为当前进程注册Crash监听
 * @param  ipc_pipe InitCrashServerOnWin的callback获得的ipc_pipe值
 * @return  当前进程是否成功注册崩溃监听
 */
bool ParfaitWrapperBase::InitCrashClientOnWin(const char* ipc_pipe);
/**
 * 仅Windows可用
 * 在parfait初始化前为当前进程注册Crash监听,参数和上面一致
 * ⚠️调用本方法后不需要再调用上面的InitCrashClientOnWin方法
 */
static bool ParfaitWrapperBase::InitCrashClientOnWinEarly(const char* ipc_pipe); 
示例代码: 二选一即可
//e.g. 二选一即可
//场景1(推荐): 子进程只需要注册崩溃监听
bool res = parfait::ParfaitWrapperBase::InitCrashClientOnWinEarly(ipc_pipe);

//场景2: 子进程还需要使用parfait其他功能
parfait sdk 初始化代码
bool res = parfait_wrapper_ptr->InitCrashClientOnWin(ipc_pipe);

特定异常捕获(可选)

原生Crashpad只能捕获走UEF的异常,无法捕获只走VEH->SEH的异常,如堆破坏。为捕获这些异常,parfait Crashpad注册了VEH,并默认捕获堆破坏异常。
VEH捕获异常后会生成dmp文件并继续将异常抛出。如果业务方需要捕获特定类型的异常,可以查阅winnt.h中异常类型错误码添加VEH目标异常类型。
注意:VEH捕获异常生成dmp后,UEF不会再次生成dump文件。崩溃监控初始化前后调用都可以。
注意:请不要随意调用此接口,会造成非预期行为,请咨询后再调用。

/**
 * 仅Windows可用
 * Crashpad VEH默认捕获Heap Corruption类型异常,业务方可自定义设置其他需要捕获的异常类型
 * VEH捕获异常后会生成dmp文件,并将异常抛出
 * @param  exception_type_code 异常的错误码,可在winnt.h中查询, e.g.STATUS_ACCESS_VIOLATION/0xC0000005L
 */
static void ParfaitWrapperBase::AddVehTargetExceptionType(const unsigned long exception_type_code);

示例代码:
parfait::ParfaitWrapperBase::AddVehTargetExceptionType(0xC0000005L);
上报Crash

初始化崩溃监听后,parfait会自动上传崩溃监听。业务无需操心。旧版本上,崩溃报告上传时机为应用重启时,崩溃报告实时上传,如上传失败,崩溃报告会留到下次重启上传。

消费Crash

Crash查询

崩溃上传后,用户可以在APMPlus PC平台的崩溃趋势下,看到刚刚上报的崩溃。

符号表上传

崩溃报告中只会记录堆栈地址,想要看到堆栈符号,需要上传符号表。
APMPlus PC会收集Windows + Mac的系统符号表,业务仅需要上传业务符号表。
APMPlus通过模块名(exe/dll/dylib/so等文件的名字)+ uuid(每次编译唯一)匹配符号表。如果崩溃详情页没有符号信息,代表没有上传模块对应的符号表。如何上传详见符号表管理

最佳实践:
  1. 初始化SDK
  2. 调用InitCrashServer/InitCrashServerOnWin,确保返回值为true
  3. 发生crash后,crash文件存储路径下会生成.dmp结尾的文件,mac/linux的在*dump_dir(业务方传入)/pending文件夹下,windows的在dump_dir(业务方传入)/reports*文件夹下。
  4. 发生crash后,应用退出,crash报告直接上传到APMPlus平台。
  5. 过两三分钟后APMPlus PC平台的Crash列表就可以查到该crash。
  6. 如果没有上传相关符号表,堆栈表示为unknown。上传相关符号表(符号表 - 仅缺失 - 上传),点击详情页的“重新解析”按钮,crash堆栈解析成功。
其他功能

崩溃附加Filter Context

上下文信息用于筛选Crash信息。筛选条件为键值对。

  1. 初始化SDK时,实例环境变量可调用ParfaitEnvBuilderBase::AddCrashContext新增CrashContext信息。如果用该实例初始化Parfait Crashpad,Crashpad会带上该实例的CrashContext信息。
  2. 初始化SDK后,可调用ParfaitWrapperBase::AddCrashContext新增或修改当前实例CrashContext信息。
  3. 不同的parfait实例,可以拥有不同的CrashContext信息。如果需要更新Parfait Crashpad中的Crash Context信息,还需要调用RefreshCrashContextInCrashServer方法。
/**
 * 添加崩溃的Context信息,用于单点展示以及崩溃列表页面的过滤
 * @param  key 键
 * @param  value 值
 */
ParfaitEnvBuilderBase& ParfaitEnvBuilderBase::AddCrashContext(const char* key, const char* value);
/**
 * 三端可用,更新当前实例的CrashContext,用于单点展示以及崩溃列表页面的过滤,上传崩溃文件/抓获崩溃时附带上传
 * 如使用了parfait的崩溃监听服务,需要再调用RefreshCrashContextInCrashServer方法更新CrashServer中的CrashContext
 * @param  key key值,不可为空
 * @param  value value值,不可为空
 */
 void ParfaitWrapperBase::AddCrashContext(const char* nonullable_key, const char* nonullable_value);
/**
 * 三端可用,更新Crashpad的CrashContext为当前实例的CrashContext
 */
 void ParfaitWrapperBase::RefreshCrashContextInCrashServer();

崩溃附加场景信息

添加场景信息后,您可筛选目标场景下的Crash:

struct ParfaitWrapperBase::CrashAnnotation {
    const char* scene = nullptr; // 当前场景
};
   
/**
 * 三端可用,崩溃后parfait crashpad会自动带上crash annotation字段信息,所有进程共享字段,可随时调用
 * e.g. 应用刚启动,设置scene为launch
 */
static void ParfaitWrapperBase::SetCrashAnnotation(const CrashAnnotation* annotation);

崩溃后回调(仅Win)

崩溃后,业务希望在客户端做一些操作,可以调用此方法。暂时仅支持windows。1.3.0.0后支持。crashpad上报完crash后,会在崩溃进程调用传入的nonnullable_crash_callback方法。谨慎使用,不当操作可能导致二次崩溃。

/**
 * @brief Type of crash callback func ptr.
 */
typedef void (*CrashCallback)();


/**
 * @brief Only avaliable on Windows temporarily.
 * Sets the Crash Callback object in current process. This callback will be called after crash. Be careful to use
 * since some operations would result in second crash.
 *
 * @param nonnullable_crash_callback callback
 */
static void ParfaitWrapperBase::SetCrashCallback(const CrashCallback nonnullable_crash_callback);

崩溃后拉起业务UI处理进程(仅Win)

在崩溃后,业务希望有更多的业务交互,可以调用此方法。暂时仅支持windows。parfait支持功能有:

  • (Required) 新起一个业务自定义进程。

  • (Optional) 拷贝当前崩溃dmp文件到业务指定目录下,同时该文件路径作为参数传递给自定义进程。

  • (Optional) Parfait是否需要依据自定义进程返回结果来决定崩溃的后续处理(上传/不上传直接删除),同时可以指定Parfait的最长等待时间。自定义进程的返回结果必须是以下三种:

/**
 * @brief List of legal authorization value returned by crash post handler.
 */
enum CrashPostHandlerResult {
  // Unknown. The dmp file will be deleted.
  Crash_Post_Handler_Result_Unknown     = 0,  
  // Allows to upload dmp file.
  Crash_Post_Handler_Result_Upload_Dump = 1,  
  // Doesn't allow to upload dmp file. The dmp file will be deleted.
  Crash_Post_Handler_Result_Delete_Dump = 2, 
}
struct CrashPostHandlerData {
  const unsigned int struct_size = sizeof(CrashPostHandlerData);
  // 必填,业务自定义处理进程地址,崩溃后会自动拉起
  const char* process_path = nullptr;  
  // 选填,拷贝dmp的目的目录,parfait会自动copy dmp到此目录下,并传递dump_path参数给自定义处理进程
  const char* dump_copy_dir = nullptr; 
  // 选填,如果为‘true’,parfait会等待自定义处理进程返回授权结果再来处理当前崩溃,等待最长时间为wait_result_timeout_s,超时后直接删除。可返回结果看CrashPostHandlerResult详情。默认'false'直接上传。
  bool wait_result_for_dump_handle = false;
  // 选填,如果wait_result_for_dump_handle为true,parfait等待的最长时间
  unsigned long wait_result_timeout_s = 120;
} 

/** 在崩溃监听进程内设置业务自定义处理进程,崩溃后parfait会帮忙拉起相应进程,并传入dmp地址(如有)
 * ⚠️注意:暂时只支持windows
 * @param  data 业务自定义处理进程相关信息
 * @return  是否成功设置
 */
 static bool ParfaitWrapperBase::SetCrashPostHandlerInCrashServer(const CrashPostHandlerData* data);