CodingTour
ARTS #204 | 回老家了

2 年没回家了,这次带 2 个娃回家感受下暑假~

Algorithm

本周选择的算法题是:[Soup Servings](https://leetcode.com/problems/soup-servings/)

use std::collections::HashMap;

impl Solution {
    pub fn soup_servings(n: i32) -> f64 {
        if n > 4800 {
            1.
        } else {
            Self::dfs(n, n, &mut HashMap::new())
        }
    }

    pub fn dfs(a: i32, b: i32, dp: &mut HashMap<(i32, i32), f64>) -> f64 {
        if let Some(&v) = dp.get(&(a, b)) {
            return v;
        }

        match (a <= 0, b <= 0) {
            (true, true) => 0.5,
            (false, true) => 0.,
            (true, false) => 1.,
            _ => {
                let result = (
                    Self::dfs(a - 100, b, dp) +
                    Self::dfs(a - 75, b - 25, dp) +
                    Self::dfs(a - 50, b - 50, dp) + 
                    Self::dfs(a - 25, b - 75, dp)
                ) / 4.0;
                dp.insert((a, b), result);
                result
            }
        }
    }
}

Review

The inside story of how ChatGPT was built from the people who made it

一个和 OpenAI 的联合创始人的访谈,关于 ChatGPT 诞生和技术内幕。很有意思的几个点:

  • 团队不理解 ChatGPT 为什么这么受外界欢迎
  • 产品的易用性比想象中要高
  • 低估了人们对 ChatGPT 政治问题的探究和关注程度
  • 现在仍然是早期阶段

Tip

diesel,一个适用于 Rust 的安全、可扩展的 ORM 和查询生成器。

Share

利用 Windows Visual Studio 解析 Electron dump 文件

Electron 作为桌面端应用最流行的框架之一,在稳定性管理方面的成熟度和其地位很不匹配,就拿崩溃解析流程来说,不同的操作系统之间差异还挺大,下面简单介绍一下如何在 Windows 平台解析崩溃。

简述一下崩溃发生的过程:当线程在运行过程中出现未捕获的异常时,会先调用 UnHandledExceptionFilter 函数过滤异常信息,然后调用 ExitProcess 函数退出正在运行的进程,此时系统 / 程序就会崩溃并生成 dump 文件来帮忙定位异常原因。

Windows 平台的 dump 文件分为两大类:内核模式 dump 和用户模式 dump。内核模式 dump 是操作系统创建的崩溃转储,最经典的就是系统蓝屏时会自动创建内核模式的 dump。用户模式 dump 进一步可以分为 fulldump 和 minidump:

  • fulldump 包含了某个进程完整的地址空间数据,以及许多用于调试的堆栈、寄存器等信息。毫无疑问,这样的 fulldump 对于事后调试非常有价值,但由于文件太大(几个 G),使得通过请求发送给开发者非常困难
  • minidump 则有许多类型,按照最常用的配置只包括了最必要的信息,用于恢复故障进程的所有线程的调用堆栈,以及查看故障时刻局部变量的值。这样的 minidump 文件通常很小(几 K ~ 几 M),通过请求发送给开发者非常容易,minidump 已成为各个平台的客户端崩溃的常用转储文件

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

Crashpad Overview Design

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

这个 dump 文件在分析时却不直观,如下图所示,我们可以清晰地看到崩溃堆栈,但只能获取到哪个模块崩溃,不清楚具体是哪一行代码导致的崩溃,无法进一步排查崩溃原因:

一般会通过 Visual Studio 等工具配合符号表来进一步分析原因,符号表是分析的基本物料,对于 Microsoft 编译器来说,这些是在构建过程中以 .pdb 文件生成在本地的。符号 (.pdb) 文件默认情况下包含以下信息:

  1. 公共符号 (所有函数、静态变量和全局变量)
  2. 负责可执行文件中代码部分的对象文件列表
  3. FPO 帧指针优化信息
  4. 局部变量和数据结构的名称和类型信息
  5. 源文件和行号信息

对 Electron 来说,一般只需要三种 pdb 符号文件:

  1. Microsoft PDB 符号
  2. Electron PDB 符号
  3. 应用程序 PDB 符号(在本地)

准备好这些文件后就可以通过 Visual Studio 来解析了。

由于 Visual Studio 只在 Windows 下可用,还得准备一台 Windows 电脑,不过由于 Electron 要构建 Windows 包的话也必须要一台 Windows 电脑,所以这个限制条件影响不大。

Visual Studio 有了后,就可以直接用它打开 dump 文件了,Visual Studio 将尽最大努力调试 dump 文件。如果 Visual Studio 找不到对应的符号,我们只需要通过菜单里的「调试 -> 选项」告诉 Visual Studio 去哪找文件即可,就像这样:

一切顺利的话,Visual Studio 将显示所有可用模块的列表,并能够清晰地看到调用堆栈信息,以及具体是哪一行代码导致崩溃。如果你的源码也在这台机器上,那配置完源代码路径后,在 Visual Studio 中打开解决方案资源管理器,然后点击「属性 -> 调试源文件」,填入源代码目录地址,这样就可以在崩溃那一行的代码直接断点,查看变量值或者调试,排查起来就很容易了。