什么是堆栈跟踪?
如果您的程序发生崩溃,说明遇到了错误并希望进行修复;此时堆栈跟踪可以帮助您。当发生崩溃时,您需要知道引起崩溃的原因(即触发崩溃的函数)。但通常直接触发崩溃的函数并不能真正显示出问题所在。因此,我们还需打印出调用前一个函数的函数……以此类推。我们追踪所有导致崩溃的函数调用直至main()
,它是(基本上)被首先调用的函数。
什么是调试符号?
当编译器生成机器码时,实际上只需要向CPU发出指令即可。问题是从一组指令中快速看出它们来自哪个 Rust 函数几乎是不可能的。因此,编译器可以在可执行文件中插入额外的信息,这些信息被 CPU 忽略,但会被调试工具使用。
其中一个重要部分是文件位置:编译器注释了每条指令来自哪个文件的哪行代码。这也意味着我们可以随后查看特定函数的定义位置。如果没有调试符号,我们就无法做到这点。
在您的堆栈跟踪中,可以看到一些文件位置:
1: 0x800c05b5 - std::sys::imp::backtrace::tracing::imp::write::hf33ae72d0baa11ed
at /buildslave/rust-buildbot/slave/stable-dist-rustc-linux/build/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:42
Rust标准库附带调试符号。因此,我们可以看到函数定义的位置(gcc_s.rs
第42行)。
如果您以调试模式编译(rustc
或cargo build
),则默认情况下会启用调试符号。但是,如果您以发布模式编译(rustc -O
或cargo build --release
),则默认情况下会禁用调试符号,因为它们会增加可执行文件的大小并且通常对最终用户不重要。您可以在特定的profile
部分中使用debug
关键字来调整是否需要调试符号。
所有这些奇怪的函数是什么?!
当您首次查看堆栈跟踪时,您可能会因看到所有奇怪的函数名称而感到困惑。不用担心,这是正常的!您感兴趣的是您代码的哪个部分触发了恐慌,但堆栈跟踪显示了所有涉及的函数。在您的示例中,您可以忽略前9个条目:它们只是处理恐慌并生成您看到的确切消息的函数。
第10个条目仍然不是您的代码,但也可能很有趣:恐慌在Vec<T>
的index()
函数中触发,当您使用[]
操作符时调用该函数。最后,条目11显示了一个由您定义的函数。但是您可能已经注意到,此条目缺少文件位置...上面的部分描述了如何解决这个问题。
堆栈跟踪怎么办?(tl;dr)
- 如果尚未激活调试符号,则激活调试符号(例如,只需在调试模式下编译)。
- 忽略堆栈跟踪顶部来自
std
和core
的任何函数。 - 查看您定义的第一个函数,找到其对应的位置并修复错误。
- 如果尚未完成,请将所有
camelCase
函数和方法名称更改为snake_case
,以遵循社区范围的样式指南。