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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 经过几个月的努力,基于Electron框架开发的新版淘宝直播推流软件终于上线了。随之而来的就是线上用户反馈的各种问题,其中最影响用户体验的当属应用崩溃问题了。当应用程序出现未 catch 的异常时就会发生崩溃,本文介绍了客户端应用崩溃的处理流程。

背景

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

image.png

image.png

主播立马按照提示输入钉钉号提交崩溃信息,并且加入钉钉群反馈应用崩溃问题。作为技术小二的我发现显示一切正常,怎么在主播端运行就遇到崩溃问题了,一定是使用方法不对。
但首要关头得赶紧解决问题,并且以后要保证应用的稳定性。作为前端工程师的我尝试开始学习如何分析和解决应用崩溃问题。
在此之前,让我们先来了解下前端处理 js error 异常的整体流程:首先 js error 异常是如何产生的,其次要如何捕获上报 js error 异常,接着如何监控并分析 js error 异常堆栈,最后是如何运用 sourcemap 来定位到具体是哪个函数的哪一行代码引起的 error,以此来解决该异常问题。
了解完前端处理异常的流程后,我们接下来开始了解客户端应用程序处理崩溃异常的流程,下文均以 Windows 平台讲述内容。


应用崩溃是如何产生的


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


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

image.png

当 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 文件来帮忙寻找异常原因。

image.png

那 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 dumpfile    HANDLE 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 整体设计图:

image.png

在 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

image.png

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


 支付宝小程序 IDE 最佳实践


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

image.png

该方案使用 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.json    app_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

image.png

由于雨燕平台在线分析崩溃存在局限性,因此我们需要把 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 将在屏幕上方区域显示有关崩溃的一般信息,并在下方显示已加载模块的列表,包括应用程序名称及其版本。

image.png

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


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

image.png

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

image.png

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

image.png

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

image.png

image.png

至此已介绍完如何使用 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


image.png

image.png

image.png

image.png

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

总结和展望

 总结


  • 客户端崩溃处理流程


  1. 首先从一个 “为什么应用就崩溃了” 的问题出发,追溯应用崩溃到底是如何产生的。
  2. 然后讲述了如何使用集成了 Crashpad 的 Electron crashReporter 捕获应用崩溃,并上报到雨燕、千鸟平台来监控应用崩溃的。
  3. 最后通过对雨燕平台在线分析崩溃及 Visual Studio / WinDbg Preview 本地分析崩溃工具的认识,让我们深刻知道 “工欲善其事,必先利其器”,以此来解决线上应用崩溃。

image.png

  • 前端 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 进程,让应用程序继续运行起来。

相关文章
|
7月前
自动检查以确保依赖项始终与使用的electron版本相匹配的小技巧
自动检查以确保依赖项始终与使用的electron版本相匹配的小技巧
109 0
|
1月前
|
安全 前端开发 Windows
Windows Electron 应用更新的原理是什么?揭秘 NsisUpdater
本文介绍了 Electron 应用在 Windows 中的更新原理,重点分析了 `NsisUpdater` 类的实现。该类利用 NSIS 脚本,通过初始化、检查更新、下载更新、验证签名和安装更新等步骤,确保应用的更新过程安全可靠。核心功能包括差异下载、签名验证和管理员权限处理,确保更新高效且安全。
38 4
Windows Electron 应用更新的原理是什么?揭秘 NsisUpdater
|
2月前
|
安全 前端开发 iOS开发
揭秘 electron-builder:macOS 应用打包背后到底发生了什么?
本文详细介绍了 Electron 应用在 macOS 平台上的打包流程,涵盖配置文件、打包步骤、签名及 notarization 等关键环节。通过剖析 `electron-builder` 的源码,展示了如何处理多架构应用、执行签名,并解决常见问题。适合希望深入了解 macOS 打包细节的开发者。
101 2
|
2月前
|
监控 前端开发 安全
谈谈我做 Electron 应用的这一两年
本文首发于微信公众号“前端徐徐”,作者徐徐分享了过去一两年间开发Electron桌面应用的经验与心得。文章详细介绍了从项目启动、技术选型到具体实施的过程,并探讨了桌面端开发面临的挑战及解决方案,如软件更新、任务队列设计、性能优化等。此外,还列举了一些特殊需求的实现方法,如静默安装、进程禁用等。作者认为,尽管桌面端开发有其独特性,但通过不断探索与实践,仍能显著提升用户体验和技术水平。
176 0
谈谈我做 Electron 应用的这一两年
|
2月前
|
XML 缓存 前端开发
Electron-builder 是如何打包 Windows 应用的?
本文首发于微信公众号“前端徐徐”,作者徐徐深入解析了 electron-builder 在 Windows 平台上的打包流程。文章详细介绍了 `winPackager.ts`、`AppxTarget.ts`、`MsiTarget.ts` 和 `NsisTarget.ts` 等核心文件,涵盖了目标创建、图标处理、代码签名、资源编辑、应用签名、性能优化等内容,并分别讲解了 AppX/MSIX、MSI 和 NSIS 安装程序的生成过程。通过这些内容,读者可以更好地理解和使用 electron-builder 进行 Windows 应用的打包和发布。
177 0
|
4月前
|
容器 iOS开发 Linux
震惊!Uno Platform 响应式 UI 构建秘籍大公开!从布局容器到自适应设计,带你轻松打造跨平台完美界面
【8月更文挑战第31天】Uno Platform 是一款强大的跨平台应用开发框架,支持 Web、桌面(Windows、macOS、Linux)及移动(iOS、Android)等平台,仅需单一代码库。本文分享了四个构建响应式用户界面的最佳实践:利用布局容器(如 Grid)适配不同屏幕尺寸;采用自适应布局调整 UI;使用媒体查询定制样式;遵循响应式设计原则确保 UI 元素自适应调整。通过这些方法,开发者可以为用户提供一致且优秀的多设备体验。
185 0
|
4月前
|
前端开发 JavaScript API
强强联手打造桌面应用新标杆:Angular与Electron的完美融合——从环境搭建到通信机制,全面解析构建跨平台应用的最佳实践与技巧
【8月更文挑战第31天】随着Web技术的进步,开发者们越来越多地采用Web技术来构建桌面应用程序。通过结合使用开源框架Electron及前沿的前端框架Angular,开发者能充分利用JavaScript、HTML和CSS打造出高性能且易维护的跨平台桌面应用。本文将详细介绍如何搭建基于Angular与Electron的开发环境,包括创建Angular项目、安装Electron及相关依赖、配置Electron主进程以及实现Angular应用与Electron间的通信等关键步骤,并最终将应用打包成多平台可执行文件,为读者提供了一套完整的解决方案以快速入门并实践这一强大技术组合。
143 0
|
Web App开发 前端开发 JavaScript
用electron打包前端应用初体验
用electron打包开发桌面应用遇到的各种问题和解决办法
221 1
|
缓存 JavaScript 前端开发
从0到1构建跨平台Electron应用,这篇文章就够了
Electron是一个可以直接开发构建跨平台应用的库,简单、快捷。 《Electron从0到1构建跨平台应用》这篇文章,我摘录了我自己在真实项目中,从开发到生成安装包的要点。
953 0
|
Linux iOS开发 MacOS
使用Electron生成安装包并打包为不同平台的应用格式
导言: 使用Electron开发桌面应用后,我们需要将其打包成适用于不同平台的安装包,例如 Mac 的`dmg`和 Windows 的`exe`。本文将介绍如何使用 Electron-forge 自动生成项目文件,并通过简单的步骤实现打包过程。
6673 0
下一篇
DataWorks