[Linux] Debug your own programs [GDB/Valgrind]

I wrote this article first in French just after sending my school projects at the end of the semester. And who says projects, says a lot of bugs and plenty of crashes and here I come like a hero to write a little memo and even help you to best understand how to debug your projects (for now, we’ll only talk about C languages, maybe there will be more languages in the future, who knows?). When you face this kind of problem (and you know, you face it EACH TIME you develop something whatever the language is), there is two kind of people:

  • The old school method: I put a lot of printf everywhere in the code to see where I stop or where it crashes, or I comment/uncomment some parts of the code to see what is bugging. If you’re here today, you mandatory came from this wonderful world of anarchy (and I know a bunch of students who are still developing this way) !
  • The right-way-to-do-it method: use one or more tools which allow you to debug (and you’ll start to like debugging :D). And here, we’ll talk about GDB and Valgrind.

Both of them helped me a lot for my project (which can be seen on my git) and also made me learn the hard way that you should not be using strncpy, NEVER, but it is not the topic of today’s article.

What if we start with a little example?

 #include <string.h>;
#include <stdio.h>;
#include <stdlib.h>;

int main (int argc, char** argv){
    char string[42] = "We love tricksandprojects.wordpress.com !";
    char* tmp = NULL;
    strcpy(tmp, string);
    puts(tmp);
    return EXIT_SUCCESS;
}

Nice segmentation fault and since the code is really short, the easy (and lazy) way is to do it step by step by guessing whatever you can guess. Imagine for one long project, first you need to identify the part of the code which is failing and only after find the error, step by step! And…

Aint-Nobody-Got-Time-for-That

Okay, here the error is kind of obvious but it is just an example and show you that both GDB and Valgrind are complementary! In that case we have a nice segmentation fault and GDB tells us:

Program received signal SIGSEGV, Segmentation fault.
__strcpy_ssse3 () at ../sysdeps/x86_64/multiarch/strcpy-ssse3.S:98
98    ../sysdeps/x86_64/multiarch/strcpy-ssse3.S: No such file or directory.

We then call the function bt (for « backtrace ») and it shows us where come from this error:

(gdb) bt
#0  __strcpy_ssse3 () at ../sysdeps/x86_64/multiarch/strcpy-ssse3.S:98
#1  0x0000000000400671 in main (argv=1, argc=0x7fffffffe828) at test_segfault.c:9

We now know that the error is at the ninth line of the file test_segfault.c. However, we still ignore what is the reason of this segfault (I know, I know, you already have figured it out but let’s assume we do not know and that’s why we are running Valgrind on our program, which gives us:

Invalid write of size 1
==7198==    at 0x4C2D7FC: strcpy (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7198==    by 0x400670: main (test_segfault.c:9)
==7198==  Address 0x0 is not stack'd, malloc'd or (recently) free'd

We have learned that our program illegaly writes in the memory an element of a size 1 which means we malloc’d a byte too small. But what should shock you is the last line: the address 0x0 (which is actually the NULL address) has no allocated memory. It simply means: you didn’t malloc you idiot (or/and lazy boy, and yes debuggers are rude).

Easy to correct:

 #include <string.h>;
#include <stdio.h>;
#include <stdlib.h>;

int main (int argc, char** argv){
    char string[42] = "We love tricksandprojects.wordpress.com !";
    char* tmp = NULL;
    tmp = malloc(sizeof(char)*strlen(string));
    strcpy(tmp, string);
    puts(tmp);
    return EXIT_SUCCESS;
}

We launch our program and I can hear you running in your room and screaming like it is the best achievement in your life: it works. But in fact, not really. Your program will effectively print We love tricksandprojects.wordpress.com ! without any bug or crash. However there is a mistake you really should correct otherwise your program could have an unexpected behavior on some devices (because of memory allocation). We can only see it with Valgrind:

==7229== Invalid write of size 1
==7229==    at 0x4C2D812: strcpy (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7229==    by 0x400718: main (test_segfault.c:9)
==7229==  Address 0x51fc069 is 0 bytes after a block of size 41 alloc'd
==7229==    at 0x4C2CD7B: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7229==    by 0x400701: main (test_segfault.c:8)
==7229== 
==7229== Invalid read of size 1
==7229==    at 0x4C2D7D4: __GI_strlen (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7229==    by 0x4EA4ECB: puts (ioputs.c:36)
==7229==    by 0x400724: main (test_segfault.c:10)
==7229==  Address 0x51fc069 is 0 bytes after a block of size 41 alloc'd
==7229==    at 0x4C2CD7B: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7229==    by 0x400701: main (test_segfault.c:8)

We see that errors are happening in the strcpy at the ninth line of test_segfault.c and in the puts at the tenth line of the same file. Please notice that the given order is the innermost to the outermost function (means the closest to the system to the closest to the user or developer). Most of the time, when an Invalid Write occurred, an Invalid Read is not that far to occur too. We now replace the eighth line of the file by:

tmp = malloc(sizeof(char)*(strlen(string)+1));

Okay, great. Now we are done. Hum… Wait, WAIT! Please run Valgrind once more to be sure:

==9691== HEAP SUMMARY:
==9691==     in use at exit: 42 bytes in 1 blocks
==9691==   total heap usage: 1 allocs, 0 frees, 42 bytes allocated
==9691== 
==9691== LEAK SUMMARY:
==9691==    definitely lost: 42 bytes in 1 blocks
==9691==    indirectly lost: 0 bytes in 0 blocks
==9691==      possibly lost: 0 bytes in 0 blocks
==9691==    still reachable: 0 bytes in 0 blocks
==9691==         suppressed: 0 bytes in 0 blocks
==9691== Rerun with --leak-check=full to see details of leaked memory
==9691== 
==9691== For counts of detected and suppressed errors, rerun with: -v
==9691== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

I knew it, I KNEW IT. Still an « error »: a memory leak.

When you allocate memory to a variable (thanks to malloc, calloc, realloc…), you should NEVER forget to free this memory space. EVERY memory allocation (malloc) has its OWN free! In your small programs, no worries it doesn’t impact anything but we can easily reach more than 20MB of memory leak for a non-optimized program which manages linked lists (my last-year project) and a friend of mine just reached the threshold of 1GB (yes, more than 1024MB, you’ve well red) of memory leak which finally helped him to crash his computer (« Well done to you, bad programmer. Feel the power of the memory leak! »). And this is just for small projects, imagine for big projects or even games: if programmers didn’t care about memory leak your RAM would decrease visibly and would slow down a lot your computer. Valgrind does not help you to find which pointer is not freed by default but you can run it with –leak-check=full to detect memory leaks as errors and it’ll give you where the non-freed pointer is malloc’d.

valgrind --leak-check=full ./mon_prog

Gives us:

==5009== 42 bytes in 1 blocks are definitely lost in loss record 1 of 1
==5009==    at 0x4C2CD7B: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5009==    by 0x400705: main (test_segfault.c:8)

The malloc made line 8 doesn’t have its free. Add a line at the end of your program:

    free(tmp);
    tmp = NULL; /*Mandatory after a free to be sure the pointer is pointing on NULL*/
    return EXIT_SUCCESS;
Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s