[Rust翻译]调试Rust中的Segfault

1,795 阅读5分钟

原文地址:blog.sentry.io/2019/12/19/…

原文作者:blog.sentry.io/authors/liz…

发布时间:2019年12月19日

本文由 简悦SimpRead 转码,原文地址 blog.sentry.io

我们开始使用我们的本地崩溃报告SDK来修复我们的本地崩溃报告服务。

如果你错过了我们关于如何使用Sentry运行、崩溃和调试Native应用程序的结对编程会议,不用担心!我们的人工智能Richard--更加智能的人工智能。我们的人工智能Richard--比它想象的要聪明得多--已经用它的特殊能力上传了我们会议的录音。

i.vimeocdn.com/video/undef…

这一次,我们选择了一个非常特殊的受害者。Symbolicator。那是在Sentry负责处理本地崩溃报告的服务。就像其他的应用程序一样,它可能会崩溃--当这种情况发生时,我们想获得对其发生原因的最佳洞察力。

我们最初认为这将是有趣的--如果它崩溃了--因为它象征着本地崩溃。德国人的幽默水平在这里发挥作用。最终,我们发现这一点都不好笑。所以我们开始修复它;也是非常德国的。

编者注:杨是德国人,所以他知道。

Crashing Rust

Symbolicator是用Rust编写的,这种语言因其内存安全而受到喜爱。除此之外,Rust标准库为显式错误处理提供了设计得非常好的API,使未经检查的异常成为过去的事情。然而,Rust程序仍然会崩溃的原因有很多,包括。

  • 使用unsafe,通常用于绝对必要的优化
  • 与C和C++的互操作,这在本质上是不安全的
  • 堆栈溢出或内存不足的情况

我们选择了第一个选项,引入了一个非常可靠的、不幸的、太出名的崩溃。臭名昭著的_分段故障_。看看这个美景吧。

unsafe { *(0 as *mut u32) = 42; }

这是我们在录音会议中使用的代码的一个小修改版本。它取消了对空指针的引用,并试图给它分配一个42的值。完全不安全,而且保证会崩溃。有趣的是:根据不同的优化级别,这一行可能会导致分段故障或非法指令。

Symbolicator已经使用了我们的Rust SDK。它能够向Sentry报告错误和恐慌,能够记录面包屑,甚至能够管理并发作用域。然而,它不能处理应用程序的硬崩溃。为此,我们需要动用大炮了。

使用本地SDK

我们的本地SDK来拯救我们! 我们选择了_Crashpad_发行版,它在应用程序崩溃时创建一个minidump。SDK建立了一个动态库,可以链接到我们的Rust应用程序。为了指示Cargo,我们在build script中添加了几行。

println!("cargo:rustc-link-lib=sentry_crashpad");
println!("cargo:rustc-link-search=path/to/lib");
println!("cargo:rustc-cdylib-link-arg=-Wl,-rpath,@executable_path/.");

这就告诉构建系统要链接我们的SDK的动态库,在构建过程中要在哪里找到它,在运行时要在哪里找到它。从现在开始,动态库需要和主可执行文件一起发布。

Native SDK暴露了一个在sentry.h头文件中声明的C语言API。我们选择编译一个小的C文件来初始化SDK并向我们的Rust代码输出一个函数。

#include <sentry.h>

void init_native(void)
{
    sentry_options_t *options = sentry_options_new();
    sentry_options_set_dsn(options, "__SENTRY_DSN__");
    sentry_options_set_debug(options, 1);
    sentry_options_set_handler_path(options, "crashpad_handler");
    sentry_init();
}

为了构建和链接这个程序,我们在构建脚本中加入了对cc板块的调用。

cc::Build::new()
    .file("native/src/init.c")
    .include("native/include")
    .compile("native")

现在,我们可以直接从Rust代码中调用C函数来初始化Native SDK。为了做到这一点,我们在 "extern "C "块中声明了函数签名。调用C函数需要使用unsafe,因为编译器不能再推理函数主体的副作用和内容了。

extern "C" {
    fn init_native();
}

unsafe {
    init_native();
}

另外,你也可以直接调用Native SDK中的函数。这需要对每个函数进行FFI绑定,这可以在构建时使用bindgen crate方便地创建。它从C头生成了一个Rust文件。虽然我们在编码过程中没有这样做--为了简短,我们一般建议直接从Rust中使用Sentry的API。

Wrapping Up

最后,我们可以把我们新的Symbolicator构建的调试信息上传到Sentry。这通常是作为我们部署过程的一部分来做的,但由于我们不打算部署这个明显有问题的Symbolicator版本--不是说我们曾经做过那样的事!我们不得不使用``````手动上传。- 我们不得不使用sentry-cli手动上传(为了便于阅读,路径被缩写)。

$ sentry-cli upload-dif --include-sources symbolicator symbolicator.dSYM

--include-sources标志使源代码可以被Sentry使用。在实时会话中,我们还使用了--wait来确保文件在下次崩溃前被Sentry处理。现在,一旦应用程序崩溃,SDK就会向Sentry发送一份Minidump崩溃报告,该报告直接指向该错误。自己看吧。

有了调试信息,Sentry就可以解决Rust函数的名称,甚至是源码上下文。在这种情况下,你可以清楚地看到堆栈跟踪是如何指向我们的不安全代码行的。真可惜,不是所有的问题都能这么容易调试。

将Native SDK添加到我们生产的Rust服务中,对我们来说是一个有趣的练习。我们希望你能和我们一起享受这次对Rust世界的短暂游览,并希望了解你的使用情况。针对C和C++,Android NDK,以及几乎所有其他可以编译成机器码的语言,sentry-native正在成为我们最通用的SDK之一。

一如既往,我们非常欢迎你在我们的论坛上发表反馈,或者向我们的支持工程师寻求帮助。


www.deepl.com 翻译