Attila's Blog

About writing your own programming language in C, Go and Swift

Checking for memory leaks with Valgrind

Sep 10, 2018 Comments

Categories: aspl

Working with C is very low level, and always means manual memory management. It is important to have at least one method in place to check for memory leaks. On MacOS, especially if you use XCode, it is quite easy to use Instruments for this. On other platforms, let’s say Linux, Valgrind is one of the best tools.

Installing Valgrind

On MacOS

The easiest way is to use Homebrew. If you don’t have it yet, install this first.

Just paste this into a terminal and you are good to go:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Next, install Valgrind:

brew install valgrind

On Linux

On a Debian based system:

sudo apt install valgrind

Or, if you prefer installing from source:

wget ftp://sourceware.org/pub/valgrind/valgrind-3.13.0.tar.bz2
tar xvjf valgrind-3.13.0.tar.bz2
cd valgrind-3.13.0
./configure
make
sudo make install

Checking memory leaks

Valgrind has a whole lot more to offer than just memory leak detection, but today I will only demonstrate this feature.

Let’s see how to check the aspl binary for memory leaks.

Open a terminal in the aspl-c/bin directory, and start the application with these parameters:

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose ./aspl

or, alternatively you can use the included script as well:

./check_memleaks.sh 
==14382== Memcheck, a memory error detector
==14382== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==14382== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
==14382== Command: ./aspl
==14382== 
--14382-- Valgrind options:
--14382--    --tool=memcheck
--14382--    --leak-check=full
--14382--    --show-leak-kinds=all
--14382--    --track-origins=yes
--14382--    --verbose
--14382-- Contents of /proc/version:
--14382--   Linux version 4.9.0-8-amd64 (debian-kernel@lists.debian.org) (gcc version 6.3.0 20170516 (Debian 6.3.0-18+deb9u1) ) #1 SMP Debian 4.9.110-3+deb9u4 (2018-08-21)
--14382-- 
--14382-- Arch and hwcaps: AMD64, LittleEndian, amd64-cx16-lzcnt-rdtscp-sse3-avx-avx2-bmi
--14382-- Page sizes: currently 4096, max supported 4096
--14382-- Valgrind library directory: /usr/lib/valgrind
--14382-- Reading syms from /home/attilahaluska/aspl-c/bin/aspl
--14382-- Reading syms from /lib/x86_64-linux-gnu/ld-2.24.so
--14382--   Considering /usr/lib/debug/.build-id/26/3f909dbe11a66f7c6233e3ff0521148d9f8370.debug ..
--14382--   .. build-id is valid
--14382-- Reading syms from /usr/lib/valgrind/memcheck-amd64-linux
--14382--   Considering /usr/lib/valgrind/memcheck-amd64-linux ..
--14382--   .. CRC mismatch (computed db5b2ec5 wanted 0eae776b)
--14382--   Considering /usr/lib/debug/usr/lib/valgrind/memcheck-amd64-linux ..
--14382--   .. CRC is valid
--14382--    object doesn't have a dynamic symbol table
--14382-- Scheduler: using generic scheduler lock implementation.
--14382-- Reading suppressions file: /usr/lib/valgrind/default.supp
==14382== embedded gdbserver: reading from /tmp/vgdb-pipe-from-vgdb-to-14382-by-attilahaluska-on-???
==14382== embedded gdbserver: writing to   /tmp/vgdb-pipe-to-vgdb-from-14382-by-attilahaluska-on-???
==14382== embedded gdbserver: shared mem   /tmp/vgdb-pipe-shared-mem-vgdb-14382-by-attilahaluska-on-???
==14382== 
==14382== TO CONTROL THIS PROCESS USING vgdb (which you probably
==14382== don't want to do, unless you know exactly what you're doing,
==14382== or are doing some strange experiment):
==14382==   /usr/lib/valgrind/../../bin/vgdb --pid=14382 ...command...
==14382== 
==14382== TO DEBUG THIS PROCESS USING GDB: start GDB like this
==14382==   /path/to/gdb ./aspl
==14382== and then give GDB the following command
==14382==   target remote | /usr/lib/valgrind/../../bin/vgdb --pid=14382
==14382== --pid is optional if only one valgrind process is running
==14382== 
--14382-- REDIR: 0x401af80 (ld-linux-x86-64.so.2:strlen) redirected to 0x3809de81 (vgPlain_amd64_linux_REDIR_FOR_strlen)
--14382-- REDIR: 0x4019830 (ld-linux-x86-64.so.2:index) redirected to 0x3809de9b (vgPlain_amd64_linux_REDIR_FOR_index)
--14382-- Reading syms from /usr/lib/valgrind/vgpreload_core-amd64-linux.so
--14382--   Considering /usr/lib/valgrind/vgpreload_core-amd64-linux.so ..
--14382--   .. CRC mismatch (computed 74a069fa wanted 84d99202)
--14382--   Considering /usr/lib/debug/usr/lib/valgrind/vgpreload_core-amd64-linux.so ..
--14382--   .. CRC is valid
--14382-- Reading syms from /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so
--14382--   Considering /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so ..
--14382--   .. CRC mismatch (computed 88f2547e wanted 8a7a4459)
--14382--   Considering /usr/lib/debug/usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so ..
--14382--   .. CRC is valid
==14382== WARNING: new redirection conflicts with existing -- ignoring it
--14382--     old: 0x0401af80 (strlen              ) R-> (0000.0) 0x3809de81 vgPlain_amd64_linux_REDIR_FOR_strlen
--14382--     new: 0x0401af80 (strlen              ) R-> (2007.0) 0x04c2ee60 strlen
--14382-- REDIR: 0x4019a50 (ld-linux-x86-64.so.2:strcmp) redirected to 0x4c2ff60 (strcmp)
--14382-- REDIR: 0x401ba90 (ld-linux-x86-64.so.2:mempcpy) redirected to 0x4c33330 (mempcpy)
--14382-- Reading syms from /lib/x86_64-linux-gnu/libc-2.24.so
--14382--   Considering /usr/lib/debug/.build-id/aa/889e26a70f98fa8d230d088f7cc5bf43573163.debug ..
--14382--   .. build-id is valid
--14382-- REDIR: 0x4ebc710 (libc.so.6:strcasecmp) redirected to 0x4a26740 (_vgnU_ifunc_wrapper)
--14382-- REDIR: 0x4eb81b0 (libc.so.6:strcspn) redirected to 0x4a26740 (_vgnU_ifunc_wrapper)
--14382-- REDIR: 0x4ebea00 (libc.so.6:strncasecmp) redirected to 0x4a26740 (_vgnU_ifunc_wrapper)
--14382-- REDIR: 0x4eba620 (libc.so.6:strpbrk) redirected to 0x4a26740 (_vgnU_ifunc_wrapper)
--14382-- REDIR: 0x4eba9b0 (libc.so.6:strspn) redirected to 0x4a26740 (_vgnU_ifunc_wrapper)
--14382-- REDIR: 0x4ebbd80 (libc.so.6:memmove) redirected to 0x4a26740 (_vgnU_ifunc_wrapper)
--14382-- REDIR: 0x4eba330 (libc.so.6:rindex) redirected to 0x4c2e7f0 (rindex)
--14382-- REDIR: 0x4ebc230 (libc.so.6:memset) redirected to 0x4a26740 (_vgnU_ifunc_wrapper)
--14382-- REDIR: 0x4f61230 (libc.so.6:__memset_avx2_unaligned_erms) redirected to 0x4c324c0 (memset)
--14382-- REDIR: 0x4eb2f10 (libc.so.6:malloc) redirected to 0x4c2bb40 (malloc)

ASPL REPL

Type 'Ctrl+C', 'quit' or 'q' to exit.

aspl> 

You will see a bunch of debug messages, and at the end you will get the normal application prompt where you can start using aspl. Type in some expressions to make aspl running, and then press q to exit the process.

aspl> (1 + 2) * 3 - -4 / 2
--14382-- REDIR: 0x4ebb5f0 (libc.so.6:memchr) redirected to 0x4c30000 (memchr)
--14382-- REDIR: 0x4ebbe90 (libc.so.6:memcpy@GLIBC_2.2.5) redirected to 0x4c300c0 (memcpy@GLIBC_2.2.5)
--14382-- REDIR: 0x4ebb940 (libc.so.6:bcmp) redirected to 0x4a26740 (_vgnU_ifunc_wrapper)
--14382-- REDIR: 0x4f7bd40 (libc.so.6:__memcmp_sse4_1) redirected to 0x4c31ca0 (__memcmp_sse4_1)
--14382-- REDIR: 0x4eb35c0 (libc.so.6:realloc) redirected to 0x4c2dd50 (realloc)
--14382-- REDIR: 0x4eb3510 (libc.so.6:free) redirected to 0x4c2cd70 (free)
--14382-- REDIR: 0x4eb8650 (libc.so.6:strlen) redirected to 0x4c2eda0 (strlen)
--14382-- REDIR: 0x4ebbe70 (libc.so.6:__GI_mempcpy) redirected to 0x4c33060 (__GI_mempcpy)
BinaryInfixOperatorExpressionNode {
  BinaryInfixOperatorExpressionNode {
    BinaryInfixOperatorExpressionNode {
      IntegerExpressionNode {
--14382-- REDIR: 0x4ec2710 (libc.so.6:strchrnul) redirected to 0x4c32e60 (strchrnul)
        1
      }
--14382-- REDIR: 0x4eba2f0 (libc.so.6:strncpy) redirected to 0x4a26740 (_vgnU_ifunc_wrapper)
--14382-- REDIR: 0x4ecc7a0 (libc.so.6:__strncpy_sse2_unaligned) redirected to 0x4c2f3b0 (__strncpy_sse2_unaligned)
      Operator +
      IntegerExpressionNode {
        2
      }
    }
    Operator *
    IntegerExpressionNode {
      3
    }
  }
  Operator -
  BinaryInfixOperatorExpressionNode {
    UnaryPrefixOperatorExpressionNode {
      Operator -
      IntegerExpressionNode {
        4
      }
    }
    Operator /
    IntegerExpressionNode {
      2
    }
  }
}
aspl> q       
Bye
==14382== 
==14382== HEAP SUMMARY:
==14382==     in use at exit: 0 bytes in 0 blocks
==14382==   total heap usage: 35 allocs, 35 frees, 3,012 bytes allocated
==14382== 
==14382== All heap blocks were freed -- no leaks are possible
==14382== 
==14382== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==14382== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
attilahaluska@attila-debian-server:~/aspl-c/bin$ 

The important bit is the HEAP SUMMARY section at the end. If it shows that all memory blocks were freed, you are good to go.

==14382== HEAP SUMMARY:
==14382==     in use at exit: 0 bytes in 0 blocks
==14382==   total heap usage: 35 allocs, 35 frees, 3,012 bytes allocated
==14382== 
==14382== All heap blocks were freed -- no leaks are possible
==14382== 
==14382== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==14382== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

As you can see, we managed to properly free all dynamically allocated memory in aspl so far.

Now, let’s make some changes to cause some leaks. Open aspl-c/src/compiler/parser/parser.c, and modify the following on line 113 from this:

token_free(get_previous_token(context));

to this:

// token_free(get_previous_token(context));

Compile aspl, and run Valgrind again with the same steps as previously. The output is quite different this time:

==14746== HEAP SUMMARY:
==14746==     in use at exit: 48 bytes in 1 blocks
==14746==   total heap usage: 35 allocs, 34 frees, 3,012 bytes allocated
==14746== 
==14746== Searching for pointers to 1 not-freed blocks
==14746== Checked 68,592 bytes
==14746== 
==14746== 48 bytes in 1 blocks are definitely lost in loss record 1 of 1
==14746==    at 0x4C2BADF: malloc (vg_replace_malloc.c:298)
==14746==    by 0x4C2DE5F: realloc (vg_replace_malloc.c:785)
==14746==    by 0x10AD6B: memory_reallocate (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x108FB2: create_token (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x109864: lexer_next_token (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x10A128: advance_to_next_token (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x10A1F6: parse_precedence (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x10A349: parse_binary_operator_expression (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x10A27A: parse_precedence (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x10A2BA: parse_expression (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x10A3E2: parse_grouping_expression (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x10A238: parse_precedence (in /home/attilahaluska/aspl-c/bin/aspl)
==14746== 
==14746== LEAK SUMMARY:
==14746==    definitely lost: 48 bytes in 1 blocks
==14746==    indirectly lost: 0 bytes in 0 blocks
==14746==      possibly lost: 0 bytes in 0 blocks
==14746==    still reachable: 0 bytes in 0 blocks
==14746==         suppressed: 0 bytes in 0 blocks
==14746== 
==14746== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
==14746== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

You see that we have a problem. It says we have 35 allocations, but only 34 frees. After that you see the trace of the leak, so you can find where the bug is:

==14746== 48 bytes in 1 blocks are definitely lost in loss record 1 of 1
==14746==    at 0x4C2BADF: malloc (vg_replace_malloc.c:298)
==14746==    by 0x4C2DE5F: realloc (vg_replace_malloc.c:785)
==14746==    by 0x10AD6B: memory_reallocate (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x108FB2: create_token (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x109864: lexer_next_token (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x10A128: advance_to_next_token (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x10A1F6: parse_precedence (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x10A349: parse_binary_operator_expression (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x10A27A: parse_precedence (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x10A2BA: parse_expression (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x10A3E2: parse_grouping_expression (in /home/attilahaluska/aspl-c/bin/aspl)
==14746==    by 0x10A238: parse_precedence (in /home/attilahaluska/aspl-c/bin/aspl)

You can see that the leak happened when we called parse_grouping_expression, so you know this is the function you should start looking at.

Other than forgetting releasing memory, you can also use Valgrind to find invalid pointer usage or to detect the use of uninitialized variables.

That’s all for today.

From this point, we have several directions we could take to continue the development, so next time I will provide some options and explain the reasoning behind the decisions I made when I chose which way to go next.

Stay tuned. I’ll be back.

Tags: memory leak valgrind

← Parsing grouping and negation Generic containers in C →

comments powered by Disqus