Removing Valgrind warnings

Posted on November 21, 2020

Prelude

I use Crystal intensively for my work, and I love the language. Simple, concise, it’s almost no bullshit whatsoever. But, I do have a very specific use of it: I make calls to a C library I’ve written. It is clearly not something a lot of people are doing, at least not directly, the crystal standard library is quite extensive.

I trust the Crystal compiler enough to get the memory stuff done, but I may have done a terrible job in the C library and I want to check.

Also, I think that valgrind is a useful tool to prevent memory errors, like corruption or leaks, and should be used intensively.

Unfortunately, I cannot just use valgrind to check for memory leaks inside my C library. When I do that, there are way too many errors.

Just a single example:

==21822== 25 errors in context 12 of 15:
==21822== Conditional jump or move depends on uninitialised value(s)
==21822==    at 0x3CA89B: GC_push_all_eager (in ./my-application)
==21822==    by 0x3DA006: GC_with_callee_saves_pushed (in ./my-application)
==21822==    by 0x3CBBC6: GC_push_roots (in ./my-application)
==21822==    by 0x3C8936: GC_mark_some (in ./my-application)
==21822==    by 0x3C1C4C: GC_stopped_mark (in ./my-application)
==21822==    by 0x3C1AF6: GC_try_to_collect_inner (in ./my-application)
==21822==    by 0x3CCAFE: GC_init (in ./my-application)
==21822==    by 0x1D47A2: *GC::init:Nil (boehm.cr:127)
==21822==    by 0x3C12C3: *Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32 (main.cr:35)
==21822==    by 0x196735: main (main.cr:114)

valgrind produces thousands of warnings like this one with Crystal code, all of them are related to the garbage collector used by the Crystal programming language.

Goal: focus on my code

A lot of warnings aren’t related to my code, valgrind has to ignore most of the warnings.

How To

In order:

  1. ask valgrind to generate suppression code with --gen-suppressions=all
  2. extract code snippets, make them a bit more generic and put them in a file
  3. ask valgrind to use the file to know what to suppress

Generate suppression code snippets

Each time valgrind encounters an error, it can produce a code snippet. Here is a minimal example:

# We start valgrind with '--gen-suppressions=all' to produce suppression code.
$ valgrind --gen-suppressions=all --leak-check=full -v ./my-application
[a lot of warnings, errors and other stuff like earlier]
==4364== 
==4364== 130 errors in context 13 of 15:
==4364== Conditional jump or move depends on uninitialised value(s)
==4364==    at 0x3CA896: GC_push_all_eager (in ./my-application)
==4364==    by 0x3DA006: GC_with_callee_saves_pushed (in ./my-application)
==4364==    by 0x3CBBC6: GC_push_roots (in ./my-application)
==4364==    by 0x3C8936: GC_mark_some (in ./my-application)
==4364==    by 0x3C1C4C: GC_stopped_mark (in ./my-application)
==4364==    by 0x3C1AF6: GC_try_to_collect_inner (in ./my-application)
==4364==    by 0x3CCAFE: GC_init (in ./my-application)
==4364==    by 0x1D47A2: *GC::init:Nil (boehm.cr:127)
==4364==    by 0x3C12C3: *Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32 (main.cr:35)
==4364==    by 0x196735: main (main.cr:114)
==4364== 
{
	<insert_a_suppression_name_here>
	Memcheck:Cond
	fun:GC_push_all_eager
	fun:GC_with_callee_saves_pushed
	fun:GC_push_roots
	fun:GC_mark_some
	fun:GC_stopped_mark
	fun:GC_try_to_collect_inner
	fun:GC_init
	fun:*GC::init:Nil
	fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
	fun:main
}

In this example, the issued code will suppress a warning for a conditional statement with the described set of function calls.

Just using the code produced as-is is almost useless. This code is way too specific, and chances are that the warnings you want to suppress are related to a particular library, an object file, or to a set of functions with well-formatted names.

Suppression Code Simplification

To get things right, the code has to be more generic.

Wildcards: *, ? and …

As we can see, the suppression code produced by valgrind will suppress warnings for a kind of memory problem and a specific function stack. To make the code more generic, wildcards can be used to match functions (fun) or object files (obj).

Example:

# This will suppress warnings for this specific stack but the last function on the stack
# could be any function with a name starting with GC_
{
   <* wildcard usage>
   Memcheck:Cond
   fun:GC_*
   fun:GC_with_callee_saves_pushed
   fun:GC_push_roots
   fun:GC_mark_some
   fun:GC_stopped_mark
   fun:GC_try_to_collect_inner
   fun:GC_init
   fun:*GC::init:Nil
   fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
   fun:main
}

For my needs in practice, this is still way too specific.

I don’t want to specify the whole stack. The wildcard “” should be used.

# This will suppress warnings for any function with a name starting with GC_
# regardless of the function stack.
# This wildcard is combined with *fun* or *obj* selectors (and other wildcards).
{
   <... wildcard usage>
   Memcheck:Cond
   ...
   fun:GC_*
}

Another example:

# This will suppress warnings for any function in object files /usr/lib/blah*
{
   <* wildcard usage with 'obj' instruction>
   Memcheck:Cond
   ...
   obj:/usr/lib/blah*
}

As we can see, there is a Memcheck instruction, telling the kind of error produced the warning. These errors can be Cond, Value8, Leak and Addr8. I don’t see a way to combine several kinds of errors into a single instruction.

Take your time. Be sure you don’t suppress useful warnings.

Use valgrind with our suppression file

Our suppression code is written in a file, named my-application.suppr for the example.

# valgrind, only with warnings for our code.
$ valgrind --suppressions=my-application.suppr --leak-check=full -v ./my-application

In case there is still useless warnings, just add more instructions. Don’t worry, the process really is straightforward, you will manage. Have fun!

This blog post was heavily inspired by this page.

Finally, my file to suppress Crystal’s Garbage Collector warnings

{
        <Garbage Collector>
        Memcheck:Cond
        ...
        fun:GC_*
}

{
        <Garbage Collector>
        Memcheck:Value8
        ...
        fun:GC_*
}

{
        <Garbage Collector>
        Memcheck:Leak
        ...
        fun:GC_*
}

{
        <Garbage Collector>
        Memcheck:Addr8
        ...
        fun:GC_*
}

The garbage collector library is written with care, functions start with GC_, very useful to easily match them all. I started with thousands of warnings, it was impossible to find any relevant information in the output. This fixes the problem.