原文地址:blog.sentry.io/2019/12/19/…
原文作者:blog.sentry.io/authors/liz…
发布时间:2019年12月19日
本文由 简悦SimpRead 转码,原文地址 blog.sentry.io
我们开始使用我们的本地崩溃报告SDK来修复我们的本地崩溃报告服务。
如果你错过了我们关于如何使用Sentry运行、崩溃和调试Native应用程序的结对编程会议,不用担心!我们的人工智能Richard--更加智能的人工智能。我们的人工智能Richard--比它想象的要聪明得多--已经用它的特殊能力上传了我们会议的录音。
这一次,我们选择了一个非常特殊的受害者。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之一。
一如既往,我们非常欢迎你在我们的论坛上发表反馈,或者向我们的支持工程师寻求帮助。