虚幻崩溃报告器

在公司内部定制引擎或者开发编辑器,时常遇到崩溃的情况, 希望可以帮助开发人员快速定位问题,需要将崩溃报告收集上传到服务器 。

虚幻自带的crash reporter 看起来满足了这个需求:
https://docs.unrealengine.com/4.26/en-US/TestingAndOptimization/CrashReporter/

但是在最新的UE5分支中并沒有看到crashreporterserver对应的配置项和工程代码, 不过有crashreporterclient工程, 因此需要分析代码做一些修改以支持。

主要思路:

  1. 查看引擎调用CrashReporter的时机和相关参数
  2. 查看CrashReporterClient的行为和参数
  3. 决定Hook修改的地方以定义自己的收集行为

CrashReporter 调用

搜索CrashReportClient 发现代码片段 , 在文件WindowsPlatformCrashContext.cpp


/**
 * Finds the crash reporter binary path. Returns true if the file exists.
 */
bool CreateCrashReportClientPath(TCHAR* OutClientPath, int32 MaxLength)
{
    auto CreateCrashReportClientPathImpl = [&OutClientPath, MaxLength](const TCHAR* CrashReportClientExeName) -> bool
    {
        const TCHAR* EngineDir = FPlatformMisc::EngineDir();
        const TCHAR* BinariesDir = FPlatformProcess::GetBinariesSubdirectory();

        // Find the path to crash reporter binary. Avoid creating FStrings.
        *OutClientPath = 0;
        FCString::Strncat(OutClientPath, EngineDir, MaxLength);
        FCString::Strncat(OutClientPath, TEXT("Binaries/"), MaxLength);
        FCString::Strncat(OutClientPath, BinariesDir, MaxLength);
        FCString::Strncat(OutClientPath, TEXT("/"), MaxLength);
        FCString::Strncat(OutClientPath, CrashReportClientExeName, MaxLength);

        const DWORD Results = GetFileAttributesW(OutClientPath);
        return Results != INVALID_FILE_ATTRIBUTES;
    };

#if WITH_EDITOR
    const TCHAR CrashReportClientShippingName[] = TEXT("CrashReportClientEditor.exe");
    const TCHAR CrashReportClientDevelopmentName[] = TEXT("CrashReportClientEditor-Win64-Development.exe");
    const TCHAR CrashReportClientDebugName[] = TEXT("CrashReportClientEditor-Win64-Debug.exe");
#else
    const TCHAR CrashReportClientShippingName[] = TEXT("CrashReportClient.exe");
    const TCHAR CrashReportClientDevelopmentName[] = TEXT("CrashReportClient-Win64-Development.exe");
    const TCHAR CrashReportClientDebugName[] = TEXT("CrashReportClient-Win64-Debug.exe");
#endif

调用有两处, 分别是 LaunchCrashReportClientReportCrashUsingCrashReportClient

在此处下断点查看调用情况 , 启动后发现立即调用到, 因为启动后立即创建了一个线程 FCrashReportingThread
再下一次断点在参数传递(可能是管道)的地方 。

断点调试

下断点, 启动Editor 后, 创建一个Demo工程, 制造一个崩溃 (C++代码空指针), 查看堆栈调用 。

Editor 启动后 CrashReport 会有一个单独的线程监控CrashEvent , 也就是启动的 CrashReporter进程


while (StopTaskCounter.GetValue() == 0)
{
  if (WaitForSingleObject(CrashEvent, 500) == WAIT_OBJECT_0)
  {
    ResetEvent(CrashHandledEvent);
    HandleCrashInternal();

    ResetEvent(CrashEvent);
    // Let the thread that crashed know we're done.
    SetEvent(CrashHandledEvent);

    break;
  }

在制造崩溃时,也Attach 到 CrashReporter 进程,崩溃后在CrashReportEditor 中点击Send And Close 则可以在 CrashReportClient 进程中捕获到断点输入 。

FReply FCrashReportClient::Submit()
{
    bSendData = true;
    StoreCommentAndUpload();
    bShouldWindowBeHidden = true;
    return FReply::Handled();
}

其中DataRouter默认的URL是L"https://datarouter.ol.epicgames.com/datarouter/api/v1/public/data"

上传堆栈:

 	[内联框架] CrashReportClientEditor-Win64-Development.exe!FCrashUploadBase::SetCurrentState(FCrashUploadBase::EUploadState::Type) 行 312	C++
>	CrashReportClientEditor-Win64-Development.exe!FCrashUploadToDataRouter::BeginUpload(const FWindowsErrorReport & PlatformErrorReport) 行 744	C++
 	CrashReportClientEditor-Win64-Development.exe!FCrashReportClient::Tick(float UnusedDeltaTime) 行 296	C++
 	[内联框架] CrashReportClientEditor-Win64-Development.exe!TDelegate<bool __cdecl(float),FDefaultDelegateUserPolicy>::Execute(float) 行 580	C++
 	[内联框架] CrashReportClientEditor-Win64-Development.exe!FTicker::FElement::Fire(float) 行 127	C++
 	CrashReportClientEditor-Win64-Development.exe!FTicker::Tick(float DeltaTime) 行 92	C++
 	CrashReportClientEditor-Win64-Development.exe!FMainLoopTiming::Tick() 行 32	C++
 	CrashReportClientEditor-Win64-Development.exe!RunWithUI(FWindowsErrorReport ErrorReport) 行 355	C++
 	CrashReportClientEditor-Win64-Development.exe!SendErrorReport(FWindowsErrorReport & ErrorReport, TOptional<bool> bNoDialogOpt, TOptional<bool> bImplicitSendOpt) 行 608	C++
 	CrashReportClientEditor-Win64-Development.exe!RunCrashReportClient(const wchar_t * CommandLine) 行 1074	C++
 	CrashReportClientEditor-Win64-Development.exe!WinMain(HINSTANCE__ * hInInstance, HINSTANCE__ * hPrevInstance, char * __formal, int nCmdShow) 行 150	C++

CrashReporter 上报参数

CrashReporterEditor 监测管道信息, 如果Engine崩溃, 那么Editor收到崩溃信息, IsCrashReportAvailable 检查, 获取到进程模块等相关信息,
然后Reporter 同步到 UI显示 ,当用户点击Send 时, 调用 Uploader, DataRouter 相关请求接口收集 。

DataRouter 和 CrashUploader 相关, 查看相应的代码即可 。

自定义流程

修改引擎或者修改CrashReporterClient
显然改Client更简单,而且作为公司内部使用分发也问题不大 。

主要修改的地方可以类似 CrashReportUpload , 新增, 通过Http服务上报ErrorReport即可 。