如何在Rust中调试内存问题?

Apa*_*hka 5 memory out-of-memory rust

我希望这个问题不是太开放.我遇到了锈,在那里我得到了内存问题的"内存不足"的呼吁next一对Iterator性状的对象.我不确定如何调试它.打印只会让我发生故障.我不熟悉ltrace等其他工具,所以虽然我可以创建一个跟踪(231MiB,pff),但我真的不知道如何处理它.这样的痕迹有用吗?抓住gdb/lldb会更好吗?还是Valgrind?

Mat*_* M. 5

一般来说,要进行调试,您可以使用基于日志的方法(通过自己插入日志,或者使用 、ltraceptrace... 等工具为您生成日志),也可以使用调试器。

请注意ltraceptrace基于 或基于调试器的方法要求您能够重现问题;我倾向于手动日志,因为我工作的行业中错误报告通常太不精确,无法立即重现(因此我们使用日志来创建重现器场景)。

Rust 支持这两种方法,并且用于 C 或 C++ 程序的标准工具集非常适合它。

我个人的方法是进行一些日志记录,以快速缩小问题发生的范围,如果日志记录不足以启动调试器进行更精细的检查。在这种情况下,我建议立即使用调试器。

Apanic被生成,这意味着通过中断对恐慌钩子的调用,您可以看到出现问题时的调用堆栈和内存状态。

使用调试器启动程序,在恐慌挂钩上设置断点,运行程序,获利。


Mar*_*ann 5

一般来说,我会尝试采用以下方法:

  1. Boilerplate减少:尝试缩小OOM的问题,以便您没有太多额外的代码.换句话说:程序崩溃越快越好.有时也可以删除特定的代码片段并将其放入额外的二进制代码中,仅用于调查.

  2. 问题规模缩小:将问题从OOM降低到简单的"太多内存",这样你实际上可以告诉某些部分浪费了一些东西,但它不会导致OOM.如果您很难分辨出问题,可以降低内存限制.在Linux上,可以使用ulimit以下命令完成:

    ulimit -Sv 500000  # that's 500MB
    ./path/to/exe --foo
    
    Run Code Online (Sandbox Code Playgroud)
  3. 信息收集:如果您的问题足够小,您就可以收集噪音水平较低的信息.您可以尝试多种方式.请记住使用调试符号编译程序.关闭优化也可能是有利的,因为这通常会导致信息丢失.两者都可以通过--release编译期间不使用标志来存档.

    • 堆分析:一种方法是使用gperftools:

      LD_PRELOAD="/usr/lib/libtcmalloc.so" HEAPPROFILE=/tmp/profile ./path/to/exe --foo
      pprof --gv ./path/to/exe /tmp/profile/profile.0100.heap
      
      Run Code Online (Sandbox Code Playgroud)

      这会向您显示一个图形,它表示程序的哪些部分会占用多少内存.有关详细信息,请参阅官方文档.

    • rr:有时很难弄清楚实际发生了什么,特别是在你创建了一个配置文件之后.假设你在第2步中做得很好,你可以使用rr:

      rr record ./path/to/exe --foo
      rr replay
      
      Run Code Online (Sandbox Code Playgroud)

      这将产生一个超级大国的GDB.与普通调试会话的不同之处在于,您不仅可以,continue而且还可以reverse-continue.基本上,您的程序是从录制中执行的,您可以根据需要来回跳转.此Wiki页面为您提供了一些其他示例.有一点需要指出的是,rr似乎只适用于GDB.

    • 良好的旧调试:有时你会得到仍然太大的痕迹和录音.在这种情况下,你可以(结合ulimit技巧)只使用GDB并等到程序崩溃:

      gdb --args ./path/to/exe --foo
      
      Run Code Online (Sandbox Code Playgroud)

      您现在应该获得一个正常的调试会话,您可以在其中检查程序的当前状态.GDB也可以使用coredumps启动.这种方法的一般问题是你不能回到过去,你不能继续执行.所以你只能看到包括所有堆栈帧和变量的当前状态.如果需要,您也可以在这里使用LLDB.

  4. (潜在)修复+重复:你有一个胶水后可能会出错,你可以尝试更改你的代码.然后再试一次.如果仍然无法正常工作,请返回步骤3再试一次.


She*_*ter 5

Valgrind 和其他工具运行良好,从 Rust 1.32 开始应该可以开箱即用。早期版本的 Rust 需要将全局分配器从 jemalloc 更改为系统的分配器,以便 Valgrind 和朋友知道如何监控内存分配。

在这个答案中,我使用 macOS 开发人员工具 Instruments,因为我在 macOS 上,但 Valgrind / Massif / Cachegrind 的工作方式类似。

示例:无限循环

这是一个程序,它通过将 1MiB Strings推入 aVec并且从不释放它来“泄漏”内存:

use std::{thread, time::Duration};

fn main() {
    let mut held_forever = Vec::new();
    loop {
        held_forever.push("x".repeat(1024 * 1024));
        println!("Allocated another");

        thread::sleep(Duration::from_secs(3));
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以看到内存随时间增长,以及分配内存的确切堆栈跟踪:

仪器内存调试

示例:引用计数中的循环

这是通过创建无限引用循环来泄漏内存的示例:

use std::{cell::RefCell, rc::Rc};

struct Leaked {
    data: String,
    me: RefCell<Option<Rc<Leaked>>>,
}

fn main() {
    let data = "x".repeat(5 * 1024 * 1024);

    let leaked = Rc::new(Leaked {
        data,
        me: RefCell::new(None),
    });

    let me = leaked.clone();
    *leaked.me.borrow_mut() = Some(me);
}
Run Code Online (Sandbox Code Playgroud)

<code>Rc</code> 泄漏的工具

也可以看看: