经过几个月的努力,基于Electron框架开发的新版淘宝直播推流软件终于上线了。随之而来的就是线上用户反馈的各种问题,其中最影响用户体验的当属应用崩溃问题了。当应用程序出现未 catch 的异常时就会发生崩溃,本文介绍了客户端应用崩溃的处理流程。

背景

主播前期花了很长时间精心策划了一场直播,在淘宝直播推流软件开播。当开播时间到来时,倒计时 3、2、1 后开始直播,一切都非常顺利。

但没想到刚直播了一会,突然弹出一个 “系统崩溃提醒” 弹框,提示应用崩溃了:

主播立马按照提示输入钉钉号提交崩溃信息,并且加入钉钉群反馈应用崩溃问题。作为技术小二的我发现显示一切正常,怎么在主播端运行就遇到崩溃问题了,一定是使用方法不对。

但首要关头得赶紧解决问题,并且以后要保证应用的稳定性。作为前端工程师的我尝试开始学习如何分析和解决应用崩溃问题。

在此之前,让我们先来了解下前端处理 js error 异常的整体流程:首先 js error 异常是如何产生的,其次要如何捕获上报 js error 异常,接着如何监控并分析 js error 异常堆栈,最后是如何运用 sourcemap 来定位到具体是哪个函数的哪一行代码引起的 error,以此来解决该异常问题。

了解完前端处理异常的流程后,我们接下来开始了解客户端应用程序处理崩溃异常的流程,下文均以 Windows 平台讲述内容。

应用崩溃是如何产生的

我们先来看下 Windows 系统的整体架构图,主要分为内核模式和用户模式。像常见的内核程序、设备驱动程序等运行在内核模式,而像系统服务进程、用户应用程序如淘宝直播主播工作台等运行在用户模式。

淘宝直播主播工作台地址:https://market.m.taobao.com/app/mtb/live-portal/download/index.html

当 Windows 系统或应用程序调用 CreateProcess 函数启动主线程或 CreateThread 启动线程时,线程函数会在如下代码中运行(下面的代码引自《Windows 核心编程》第 25 章未处理异常和 C++ 异常):

// 主线程启动函数
// CreateProcess 启动线程函数
VOID BaseProcessStart(PPROCESS_START_ROUTINE pfnSatrtAddr)
{__try {ExitThread((pfnSatrtAddr)());}__except(UnHandledExceptionFilter(GetExceptionInformation())) {ExitProcess(GetExceptionCode());}
}// CreateThread 启动线程函数
VOID BaseThreadStart(PTHREAD_START_ROUTINE pfnSatrtAddr, PVOID pvParam)
{__try {ExitThread((pfnSatrtAddr)());}__except(UnHandledExceptionFilter(GetExceptionInformation())) {ExitProcess(GetExceptionCode());}
}

从上面的代码可以看出,当线程运行中出现未捕获异常时,会调用 UnHandledExceptionFilter 函数来过滤异常信息,然后调用 ExitProcess 函数退出进程的运行。也就是说,当线程中出现未 catch 的异常(类比前端 js error 未 catch)时,比如主播在使用绿幕大屏添加超大尺寸图片时应用程序 memcpy 失败但未 catch 异常导致崩溃、系统内核异常导致蓝屏现象,系统 / 程序就会崩溃并生成 dump 文件来帮忙寻找异常原因。

那 dump 文件究竟是什么呢?Windows 平台 dump 文件分为两大类:内核模式 dump 和用户模式 dump。内核模式 dump 是操作系统创建的崩溃转储,最经典的就是系统蓝屏时会自动创建内核模式的 dump。用户模式 dump 进一步可以分为 fulldump 和minidump。fulldump 包含了某个进程完整的地址空间数据,以及许多用于调试的堆栈、寄存器等信息。毫无疑问,这样的 fulldump 对于事后调试非常有价值,但由于文件太大(几G字节)使得通过请求发送给开发者非常困难。而 minidump 则有许多类型,按照最常用的配置只包括了最必要的信息,用于恢复故障进程的所有线程的调用堆栈,以及查看故障时刻局部变量的值。这样的 minidump 文件通常很小(只有几K ~ 几M字节),通过请求发送给开发者非常容易。

事实证明,minidump 已成为各个平台的客户端崩溃的常用转储文件(类比 js stack trace)。下面的代码就是定义了 pExceptionFilter 函数,当捕获到应用崩溃时调用该函数生成 minidump 文件。

LONG WINAPI pExceptionFilter(struct _EXCEPTION_POINTERS*pExceptionInfo){// create dumpfileHANDLE hFile = ::CreateFile(m_dumpFilePathFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);// set exception info_MINIDUMP_EXCEPTION_INFORMATION info;info.ThreadId = ::GetCurrentThreadId();info.ExceptionPointers = pExceptionInfo;info.ClientPointers = true;// generate mini dump contents::MiniDumpWriteDump(::GetCurrentProcess(),::GetCurrentProcessId(),lFile,MiniDumpNormal,&info, NULL, NULL);// close file::CloseHandle(hFile);// return exception handled.return EXCEPTION_EXECUTE_HANDLER;
}

如何捕获上报应用崩溃

通过上文我们了解到,当应用程序出现未 catch 的异常时就会发生崩溃。那我们要如何捕获到应用程序的崩溃呢?

  在客户端中捕获应用崩溃

经过调研发现,Google 已经开发了一个名为 Breakpad 的库及其继任者 Crashpad,它能够在各种操作系统和 CPU 架构上生成 minidump 文件。由于 Electron 使用 Crashpad 而不是 Breakpad 来捕获和上传崩溃信息,因此我们重点介绍下 Crashpad 原理。

Breakpad地址:https://chromium.googlesource.com/breakpad/breakpad/

Crashpad地址:https://chromium.googlesource.com/crashpad/crashpad/

Crashpad 是一个用于从客户端应用程序捕获、存储和上传崩溃信息到服务器的库,旨在使客户端能够以尽可能高的保真度和覆盖范围,以最小成本捕获崩溃时的进程状态。

Crashpad 还为客户端提供了最小的工具,以使用每个进程键 / 值对形式来封装他们的崩溃,而且客户端还能够通过扩展点进一步扩充崩溃报告。下图是 Crashpad 整体设计图:

在 Windows 平台,操作系统在崩溃线程的上下文中调度异常。为了通知异常处理程序,Crashpad 客户端在客户端进程中注册一个 UnhandledExceptionFilter (UEF)。当异常传递到 UEF 时,它将异常信息和崩溃线程的 ID 存储在向处理程序注册的 ExceptionInformation 结构中。然后它设置一个事件句柄来通知处理程序继续处理异常。当发生崩溃时,Crashpad最终会调用 generate_dump 来生成一个包含正在运行的进程快照的 minidump 文件。

  在 Electron 中捕获应用崩溃

幸运的是,Electron 框架已经集成了它,当主进程 / 渲染进程崩溃时,内置的崩溃报告器会自动创建 minidump 文件。作为开发者使用起来也很简单,只需要几行 JavaScript 代码就可以使用它来捕获崩溃并把 minidump 文件上传到远程服务器中(类比 js error catch 上报),为下一步监控和分析治理崩溃问题做准备。

import { crashReporter } from 'electron'crashReporter.start({productName: 'YourName',companyName: 'YourCompany',submitURL: 'https://your-domain.com/url-to-submit',uploadToServer: true
})

到现在为止,我们只是写了上面几行代码,只知道可以用来捕获和上传崩溃文件到服务器上。但崩溃文件到底是长啥样的呢?让我们来一探究竟。

由于 Electron crashReporter 底层是通过 http/https post 来上传崩溃日志的请求,于是我们可以通过 wireshark 抓包工具抓到 Electron 捕获崩溃并上传至服务器的请求,这就是 dump 文件的二进制内容。

wireshark地址:https://www.wireshark.org/download.html

揭开庐山真面目之后,我们还是不认识这堆二进制内容。但至少迈进了一步,我们已经感知到它的存在了。接下来就先把它统一监控起来吧。

如何监控应用崩溃

那要怎么把线上用户崩溃问题都监控起来呢?目前提供了两个选择:分别是为蚂蚁前端开发者打造的实时监控告警雨燕平台(背后使用的是支付宝小程序 IDE 最佳实践方案)、阿里集团为开发者提供桌面客户端标准化解决方案(包括崩溃管理)的千鸟平台(类比 arms / JsTracker 监控平台)。

  支付宝小程序 IDE 最佳实践

支付宝小程序 IDE 最佳实践方案的整体捕获上报流程图如下:

该方案使用 Electron 自带的 crashReporter 机制来捕获应用程序崩溃,然后利用阿里云 FC 来解析 dump 文件,最终将崩溃结果上报到雨燕平台进行监控。

在这个过程中,该方案基于大量的字段约定来实现信息收集:

  1. 本次崩溃明细 -> 雨燕监控的每一条记录可查看明细,如版本、平台等。

  2. 崩溃堆栈 -> 从明细中可以进入查看堆栈页面,或者在监控首页点击某条记录的分析。

  3. 用户环境信息、程序启动参数信息,上报的原始 payload,minidump 解析报告、文件等 -> 可在 HI 文件管理中在线查看,上报到 HI 的文件名有当前用户 ID,用户自定义 ID 等,可以通过搜索来定位到具体文件。

作为开发者来说,接入成本也很低,只需要写下面几行 JavaScript 代码并在主进程中 start 一次即可。

import { crashReporter } from 'electron'crashReporter.start({productName: 'TaoBaoLive',submitURL: 'https://hpmweb.alipay.com/minidump',uploadToServer: true,globalExtra: {yuyanId: '180020010101205937',yuyanEnv: 'prod',yuyanCode: '12',// app_前缀的上报在appInfo.jsonapp_liveId: LIVE_ID,HIPrefix: `tblive-crashreport-${USER_ID}`,USER_IDENTIFIER: USER_ID,},
})

  千鸟平台

千鸟平台也是基于 Electron 的 crashReporter 模块进行了一个封装,内置了千鸟的崩溃上传逻辑。作为开发者来说,接入成本也很低,也只需要写下面几行 JavaScript 代码并在主进程中 start 一次即可。

import { startCrashReporter } from '@ali/qianniao-crash-reporter'startCrashReporter.start({productName: 'TaoBaoLive',submitURL: 'https://qianniao.alibaba.com/api/open/crash/record',globalExtra: {userId: USER_ID,},
})

如何分析应用崩溃

幸好有雨燕、千鸟平台,我们可以很方便地把应用崩溃问题监控起来,接下来就要开始分析这些崩溃日志了。这里介绍两种分析 dump 崩溃文件的方案:雨燕平台在线分析崩溃和 Visual Studio / WinDbg Preview 本地分析崩溃。

  雨燕平台在线分析崩溃

雨燕平台在线分析崩溃功能是基于开源项目 electron-minidump 实现解析 minidump 文件,获取崩溃异常错误的堆栈信息。如下图所示,我们可以清晰地看到崩溃堆栈,但只能获取到哪个模块崩溃,不清楚具体是哪一行代码导致的崩溃,无法进一步排查崩溃原因。

electron-minidump地址:https://github.com/nornagon/electron-minidump

由于雨燕平台在线分析崩溃存在局限性,因此我们需要把 dump 文件下载到本地,继续用 Visual Studio / WinDbg Preview 等工具进一步分析是哪一行代码导致的崩溃。

  Visual Studio / WinDbg Preview 本地分析崩溃

在我们使用 Visual Studio / WinDbg Preview 等工具本地分析崩溃文件之前,我们有必要简要了解下符号是什么。符号是调试和其他诊断工具的基本要求,对于 Microsoft 编译器,这些是作为构建的一部分生成的 .pdb 文件。符号 (.pdb) 文件默认情况下包含以下信息(类比 js SourceMap 文件):

  1. 公共符号 (所有函数、静态变量和全局变量)

  2. 负责可执行文件中代码部分的对象文件列表

  3. FPO 帧指针优化信息

  4. 局部变量和数据结构的名称和类型信息

  5. 源文件和行号信息

符号地址:https://learn.microsoft.com/zh-cn/windows/win32/dxtecharts/debugging-with-symbols?redirectedfrom=MSDN

那为什么我们需要符号呢?如果没有 PDB 文件,调试器无法解析函数名称、参数或任何存储在堆栈上的局部变量,无法将应用程序中执行的指令与原始源代码相关联。符号对于调试非常重要,根据我们正在调试的内容,可能需要符号来显示完整的调用堆栈,并使用 Watch 窗口或 DataTips 来检查对象。如果我们正在调试不包含堆的转储文件,调试器将需要访问原始二进制文件,以便确定要加载的正确符号文件。换句话说,如果我们正在调试没有堆信息的转储,则需要符号路径上的相应二进制文件和符号文件。

对于调试非常重要:https://learn.microsoft.com/zh-cn/previous-versions/visualstudio/visual-studio-2015/debugger/specify-symbol-dot-pdb-and-source-files-in-the-visual-studio-debugger?view=vs-2015&redirectedfrom=MSDN

对应 Electron 开发的应用程序来说,一般只需要以下三种 PDB 符号文件:

  1. Microsoft PDB 符号服务器:https://msdl.microsoft.com/download/symbols

  2. Electron PDB 符号服务器:https://symbols.electronjs.org

  3. 应用程序 PDB 符号(本地 PDB 文件路径)

了解完符号文件后,我们就可以开始用 Visual Studio / WinDbg Preview 等工具分析调试本地崩溃文件了。

  • Visual Studio 本地分析崩溃

首先安装 Visual Studio 后打开 dump 文件,Visual Studio 将在屏幕上方区域显示有关崩溃的一般信息,并在下方显示已加载模块的列表,包括应用程序名称及其版本。

Visual Studio地址:https://code.visualstudio.com/docs/?dv=win‍

接着点击 “使用仅限本机进行调试” 按钮,Visual Studio 将尽最大努力调试 dump 文件,但没有显示任何有用的调用堆栈信息。

为了获取调用堆栈信息,我们将告诉 Visual Studio 在哪里可以找到二进制文件。首先,打开调试菜单并选择选项。然后单击符号,把 Microsoft PDB 符号服务器、Electron PDB 符号服务器、应用程序 PDB 符号(本地 PDB 文件路径)都填进去。

现在 Visual Studio 将显示所有可用模块的列表,并能够清晰地看到调用堆栈信息,以及具体是哪一行代码导致崩溃。

若要继续调试崩溃代码以及查看调用函数变量等信息,还需要配置源代码路径。在 Visual Studio 中打开解决方案资源管理器,然后点击属性->调试源文件,填入源代码目录。这样就可以在崩溃那一行代码断点下来查看变量值或者断点调试都行,崩溃问题排查起来就很容易了。

至此已介绍完如何使用 Visual Studio 工具分析崩溃文件的流程。

  • WinDbg Preview 本地分析崩溃

接下来再分享一个更轻量化的分析崩溃工具 WinDbg Preview。该工具可用于调试 Windows 内核模式和用户模式代码、分析故障 dump 文件以及在代码执行时检查 CPU 寄存器。

首先安装 WinDbg Preview 后打开 dump 文件,WinDbg Preview 将在屏幕上方区域显示有关崩溃的一般信息。

WinDbg Preview地址:https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-tools

为了获取调用堆栈信息,我们将告诉 WinDbg Preview 在哪里可以找到二进制文件。打开 Settings 设置菜单并选择调试设置选项。然后把 Microsoft PDB 符号服务器、Electron PDB 符号服务器、应用程序 PDB 符号(本地 PDB 文件路径)都填进去。

现在运行下 !analyze -v 命令,WinDbg Preview 将显示所有可用模块的列表,并能够清晰地看到调用堆栈信息,以及具体是哪一行代码导致崩溃。

若要继续调试崩溃代码以及查看调用函数变量等信息,还需要配置源代码路径。打开 Settings 设置菜单并选择调试设置选项。然后填入源代码目录。这样就可以在崩溃那一行代码断点下来查看变量值或者断点调试都行,崩溃问题排查起来就很容易了。

至此已介绍完如何使用 WinDbg Preview 工具分析崩溃文件的流程。

总结和展望

  总结

  • 客户端崩溃处理流程

  1. 首先从一个 “为什么应用就崩溃了” 的问题出发,追溯应用崩溃到底是如何产生的。

  2. 然后讲述了如何使用集成了 Crashpad 的 Electron crashReporter 捕获应用崩溃,并上报到雨燕、千鸟平台来监控应用崩溃的。

  3. 最后通过对雨燕平台在线分析崩溃及 Visual Studio / WinDbg Preview 本地分析崩溃工具的认识,让我们深刻知道 “工欲善其事,必先利其器”,以此来解决线上应用崩溃。

  • 前端 js error 处理流程

如果是前端工程师第一次接触客户端应用程序崩溃处理流程可能会有点困难,为了降低理解成本,我们可以类比下客户端应用程序处理崩溃和前端处理 js error 的流程。

  1. 首先客户端 dump 产生类比 js error 异常。

  2. 然后 dump 捕获上报类比 js error catch 上报。

  3. 接着雨燕 / 千鸟监控平台类比 arms / JsTracker 监控平台。

  4. 最后 dump 文件类比 js stack trace,pdb 调试符号文件类比 js SourceMap 文件。

对于前端工程师来说,我们也要了解 js error 异常是如何产生的,如何进行捕获上报并实现监控,如何分析 js 堆栈及运用 sourcemap 来定位到具体是哪个函数的哪一行代码引起的 error,以此来解决该异常问题。

了解完前端处理 js error 的整体流程后,有没有觉得跟前面介绍的客户端应用处理崩溃流程基本一致呢?其实各个端上处理异常(不仅限于处理异常)的流程都是相通的,学会迁移学习才能够快速掌握其他领域的知识,是不是突然觉得跨端门槛也没有想象中那么高了呢?

  展望

即使我们使劲浑身解数通过各种工具分析治理应用崩溃问题、及时升级 Electron 版本等,只要应用程序运行的那一刻开始,崩溃问题就永远无法避免。

因此,我们还需要实现一个独立于 Electron 主进程的守护进程。在  Electron 应用程序启动时该守护进程就可以随之启动,然后它来守护 Electron 进程。如果 Electron 进程崩溃时,就由它来启动 Electron 进程,让应用程序继续运行起来。

团队介绍

我们是大淘宝技术内容前端团队,核心负责淘宝的内容业务(直播、图文、短视频)和内容平台建设,主要业务包括淘宝直播、逛逛、亲拍,并且也通过平台化的方式支持着阿里系的其他内容业务,包括饿了么、盒马、优酷、闲鱼、飞猪等 24 个 BU,160 个业务场景。
内容化是一个较新的战场,整个前端团队在多媒体、机器学习、播放器、视频剪辑、LowCode 等技术领域都有比较多挖掘和技术应用。

¤ 拓展阅读 ¤

3DXR技术 | 终端技术 | 音视频技术

服务端技术 | 技术质量 | 数据算法

如何治理 Electron 版本淘宝直播应用崩溃?相关推荐

  1. 媒体智能-淘宝直播流媒体互动实践 | D2 分享视频+文章

    背景:今天给大家带来的分享主题是<媒体智能-淘宝直播流媒体互动实践>,内容分为5个部分,首先看看在淘宝直播的直播间里主播可以怎样给用户拜年:然后具体讲如何制作一个手势拜年的特效:接着介绍我 ...

  2. 淘宝直播在智能互动领域的探索及落地

    简介: 随着带宽成本的降低和端上算力的提升,在直播.短视频中基于流的互动玩法也将越来越丰富:无论是基于人脸.手势.分割算法的智能化贴纸,还是结合算法的小游戏有了越来越好的舞台:业界短视频和直播主要针对 ...

  3. 淘宝直播PC客户端的hybrid探索

    如何兼具"Native App良好交互体验的优势"和"Web App跨平台开发的优势"在当前互联网时代保持高效的业务迭代是一个非常重要的课题. 什么是hybri ...

  4. 深度排序模型在淘宝直播的演进与应用

    分享嘉宾:纪志辉 阿里巴巴 算法专家 编辑整理:李春雷 出品平台:DataFunTalk 导读:淘宝直播近两年在排序模型上进行了持续的迭代优化和演进落地,在多目标学习.跨场景迁移.召回匹配.大促GMV ...

  5. 罗永浩回应被中消协点名;传前淘宝直播运营负责人因贪污被阿里通报;TypeScript 4.0 Beta发布​ | 极客头条...

    整理 | 屠敏 头图 | CSDN 下载自视觉中国 快来收听极客头条音频版吧,智能播报由出门问问「魔音工坊」提供技术支持. 「极客头条」-- 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极客 ...

  6. 归因分析:淘宝直播数据助理及其价值研究

    导读:近年来,直播带货成为一种新的流行业态.主播在直播间与线上消费者实时互动,这极大的提高了信息交流的效率.在这种实时性极强的场景,主播对直播间实时信息的使用有可显著影响直播间的带货情况. 为了让主播 ...

  7. 淘宝直播商品识别大赛

    赛题链接: https://tianchi.aliyun.com/competition/entrance/231772/information 开篇之前,感谢队友的倾情付出,以及不离不弃.把我队友的 ...

  8. 淘宝/天猫按分类搜索淘宝直播接口 API 返回值说明

    淘宝直播是阿里巴巴推出的直播平台,定位于"消费类直播",用户可边看边买,涵盖的范畴包括母婴.美妆等. 随着越来越多的商家加入淘宝直播,就会发现许多主播面临着这样的问题:淘宝直播新人 ...

  9. 淘宝/天猫获取淘宝直播分类id接口 API 返回值说明

    item_videolist_cat-获取淘宝直播分类id接口 [查看演示] API测试工具 注册开通 onebound.taobao.item_videolist_cat 公共参数 请求地址: ht ...

最新文章

  1. 透明物体分割学习笔记
  2. RPM方式安装MySQL5.6
  3. 软件构造学习笔记-实验4
  4. 【笔记目录1】【jessetalk 】ASP.NET Core快速入门_学习笔记汇总
  5. delphi64位 char数组转换string中文乱码_使用位运算、值交换等方式反转java字符串-共四种方法...
  6. 安卓系统分屏相关修改思路
  7. web 自动化测试 selenium基础到应用(目录)
  8. 用git上传自己的第一个github项目
  9. 天猫精灵X1获德国红点设计大奖
  10. java环境变量代表的含义_java环境变量的含义
  11. html span设置外边距,行内元素内外边距探究:为何span设置上下margin和padding不起效...
  12. 中国内蒙古医企在“吴哥国际医院”开展“千人一对一国际医疗捐助”
  13. AD 20 PCB 导入CAD图形错乱-问题笔记
  14. pytorch复现RRU-Net
  15. MAMP(win) Apache启动失败__句柄无效。 : master_main: create child process failed. Exiting.
  16. EasyUI Menu 菜单和按钮
  17. CSS超链接标记大全
  18. ZZULIOJ:1125: 上三角矩阵的判断
  19. 工业相机概述-选型事项-生产厂家汇总
  20. word中图片不显示怎么办

热门文章

  1. ES的mapping配置详解
  2. [渝粤教育] 信阳师范学院 视听语言 参考 资料
  3. c语言已知加速度求位移速度,知道初速度知道加速度求位移的公式
  4. MoveIt!入门教程-简介
  5. 软件测试知识点合集总结
  6. 19.1 快速幂的定义和模板
  7. 哪些场景N1 mode是disable状态
  8. php curl返回body,获取CURL中返回的body内容
  9. 计算机本体应用,本体评估方法研究综述
  10. 2022年第十四届蓝桥杯模拟赛【核酸日期】C语言详解