Valgrind with Rust - Checking memory leaks in your ffi library

13 March 2017 📝 creativcoder
#profiling#rust#memory#valgrind

If you are someone writing Rust wrappers for C libraries, then you might as well wanna verify that you are doing the right cleanups in your destructors or you might ignorantly create potential memory leaks from usage of your library. In this post we'll see how to use the very same tool you use in your C programs, i.e., Valgrind to check for memory leaks in your wrapper library written in Rust.

In order to check for memory leaks we should first have code that does the leaking. Just to keep it brief we won't show a FFI library example, but we'll see a tiny example library exposing a method that does some allocation and forgets to release memory at the end of its scope (using mem-forget).

So here's our small library called leaky_lib with just one API called allocate().

[lib.rs]
use std::mem;

pub fn allocate() {
    use std::mem;
    let bad_vec: Vec<char> = Vec::with_capacity(1024);
    for _ in 0..1024 {
        bad_vec.push('0');
    }
    mem::forget(bad_vec);
}

[main.rs]
fn main() {
    allocate();
}

Here, allocate() allocates memory on the heap then we deliberately tell it to forget about that allocation thereby explicitely causing memory leak.

Now if we run our program with valgrind --leak-check=full ./target/release/leaky_lib

creativcoder% valgrind --leak-check=full ./target/release/leaky_lib 
==2488== Memcheck, a memory error detector
==2488== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==2488== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==2488== Command: ./target/release/leaky_lib
==2488== 
==2488== 
==2488== HEAP SUMMARY:
==2488==     in use at exit: 0 bytes in 0 blocks
==2488==   total heap usage: 6 allocs, 6 frees, 2,000 bytes allocated
==2488== 
==2488== All heap blocks were freed -- no leaks are possible
==2488== 
==2488== For counts of detected and suppressed errors, rerun with: -v
==2488== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

To our surprise, the leak doesn't show up anywhere in valgrind logs. What is happening here :O ?

The reason behind this is with the use of jemalloc being used as the default allocator in rustc, which as of now doesn't play well with valgrind (See details on this issue)

But fear not, Rust also allows us to fallback to OS's default allocator APIs i.e., the free and malloc. This can be opted in by using compiler attributes at your crate root (lib.rs) as shown below.

NOTE: Switching allocators only works for nightly release as of now. So just do a rustup override set nightly to switch to nightly toolchain

#![feature(alloc_system)]
extern crate alloc_system;

Once we have the above attribute in place, let's try running valgrind again. (Some lines omitted for brevity)

creativcoder% valgrind --leak-check=full ./target/release/leaky_lib
==3198== Memcheck, a memory error detector
==3198== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==3198== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==3198== Command: ./target/release/leaky_lib
==3198== 
==3198== 
==3198== HEAP SUMMARY:
==3198==     in use at exit: 4,096 bytes in 1 blocks
==3198==   total heap usage: 15 allocs, 14 frees, 6,339 bytes allocated
==3198== 
==3198== LEAK SUMMARY:
==3198==    definitely lost: 4,096 bytes in 1 blocks
==3198==    indirectly lost: 0 bytes in 0 blocks
==3198==      possibly lost: 0 bytes in 0 blocks
==3198==    still reachable: 0 bytes in 0 blocks
==3198==         suppressed: 0 bytes in 0 blocks
==3198== 
==3198== For counts of detected and suppressed errors, rerun with: -v
==3198== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Aha! this time we can see our leaks...

Now let's quickly remove our explicit leaky intentions from the code. By that I mean removing the mem::forget(v) and re-run this again.

creativcoder% valgrind --leak-check=full ./target/release/leaky_lib
==3837== Memcheck, a memory error detector
==3837== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==3837== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==3837== Command: ./target/release/leaky_lib
==3837== 
==3837== 
==3837== HEAP SUMMARY:
==3837==     in use at exit: 0 bytes in 0 blocks
==3837==   total heap usage: 15 allocs, 15 frees, 6,339 bytes allocated
==3837== 
==3837== All heap blocks were freed -- no leaks are possible
==3837== 
==3837== For counts of detected and suppressed errors, rerun with: -v
==3837== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Cool, no more memory leaks and we're saved. Have a great day!

Hey, welcome! I write about Rust, Web, Backend engineering, Distributed systems and more.
Want to stay updated on my next post? Consider subscribing!