
Daniel is a system programmer & web developer with 10 years of experience...
system programmer & web developer
coder . cl
a journey in software development...
interpreting valgrind messages
by Daniel Molina Wegener on 15-08-2009
In other posts I’ve presented you some ways to detect memory leaks, conceptually rather than practically, but enough to understand them and solve those programming issues. Normally, valgrind messages. There are some posts about valgrind and well documented user guides, such as the guide written by Alex Ott. This article is about on how to interpret valgrind messages while you are using it as memory allocation debugger.
First of all, I want to share a small script with the flags that I’m using with valgrind. Here is a small script which runs valgrind on the given program and creates a log with the valgrind output. You can find the explanation on the flags in the Alex’s article and the valgrind documentation.
#!/bin/bash
if test "$#" "<" "1"; then
echo "$0: too few arguments"
exit 1
fi
VLG=`which valgrind`
VLG_LOG=`basename $PWD`
VLG_LOG="log.$VLG_LOG"
VLG_OPTS="$VLG_OPTS --verbose"
VLG_OPTS="$VLG_OPTS --trace-children=yes"
VLG_OPTS="$VLG_OPTS --track-fds=yes"
VLG_OPTS="$VLG_OPTS --run-libc-freeres=yes"
VLG_OPTS="$VLG_OPTS --logfile-fd=1"
VLG_OPTS="$VLG_OPTS --num-callers=100"
VLG_OPTS="$VLG_OPTS --error-limit=no"
VLG_OPTS="$VLG_OPTS --show-below-main=yes"
VLG_OPTS="$VLG_OPTS --pointercheck=yes"
VLG_OPTS="$VLG_OPTS --demangle=yes"
VLG_OPTS="$VLG_OPTS --leak-check=yes"
export VLG VLG_LOG VALGRIND_OPTS
if test -r "$VLG_LOG"; then
echo "$0: removing $VLG_LOG"
rm -f -v "$VLG_LOG"
fi
$VLG $VLG_OPTS $* 2>&1 | tee "$VLG_LOG"
This shell script the call to valgrind — IMO — with the best options that some programs can have to debug them. This command line will generate a large trace on your command and as I’ve posted in a previous topic, valgrind replaces the allocation system calls with his own group of system calls, wrapping the original ones, just track those calls.
invalid read of size nnnn
Your pointer arithmetics are bad. Yes, this message means that the program is accessing an invalid address by reading nnnn bytes from it. The code bellow — that seems to be ridiculous, but is not different that the error that you have in your code — would throw this message.
#include <stdio.h>
int
main (int c, char **argv) {
char test[] = "hola mundo!";
printf ("%s\n", (char *)(test + 20));
return 0;
}
The message obtained is as follows.
==22111== 1 errors in context 1 of 1:
==22111== Invalid read of size 1
==22111== by 0x3010A4E0: vfprintf (in /lib/libc.so.6)
==22111== by 0x300F6B2F: printf (in /lib/libc.so.6)
==22111== by 0x8048571: main (test-invalid-read.c:11)
As you, see in the backtrace, the part where it reaches the invalid address is inside the printf(3) function, but the previous step is on the line 11 of the file test-invalid-read.c.
use of uninitialised value of size nnnn
By default, there is no automatic variable initialization for certain kinds of variables, all depends on the compiler implementation and the C99 standard reveals it as undefined behavior. Since that condition, is required that you initialize all your variables to ensure code portability.
#include <string.h>
#include <stdio.h>
int
main (int c, char **argv) {
char *tst;
printf ("%s\n", tst);
return 0;
}
The code above will produce the next error messages on valgrind.
==22309== 1 errors in context 1 of 6:
==22309== Use of uninitialised value of size 4
==22309== at 0x30039DC0: memcpy (in /usr/local/lib/valgrind/vgpreload_memcheck.so)
==22309== by 0x3010A4E0: vfprintf (in /lib/libc.so.6)
==22309== by 0x300F6B2F: printf (in /lib/libc.so.6)
==22309== by 0x804854B: main (test-invalid-read.c:8)
==22309==
==22309== 1 errors in context 2 of 6:
==22309== Conditional jump or move depends on uninitialised value(s)
==22309== at 0x30039DAF: memcpy (in /usr/local/lib/valgrind/vgpreload_memcheck.so)
==22309== by 0x3010A4E0: vfprintf (in /lib/libc.so.6)
==22309== by 0x300F6B2F: printf (in /lib/libc.so.6)
==22309== by 0x804854B: main (test-invalid-read.c:8)
Both errors are pointing to the same programming error. The variable tst was not initialized, and then you operate — and what makes one of the most ugliest errors on programming — using it as address reference. Also, by default gcc has automatic initialization of those pointers to null, and then you get "Null References: The Billion Dollar Mistake" pointed by Tony Hoare. Remember that variable initialization is an undefined behavior according to the current C standard. Reference verification and initialization is an important task, recently was announced a new version of Eiffel — a pure object oriented paradigm based language — that is avoiding null deferencing.
process terminating with default action of signal nn (SIGNAME): dumping core
Here the valgrind execution stops. Why? the program receives a signal and is terminated by the operating system. Take a look on the signal(3) manual page for more details.
The operating system generates a core file, from where you can get the proper information about the crashing instruction. All depends on the signal that the program was catching, a SIGBUS or bus error, means that you are writing bytes to wrong address. The code bellow shows that bug.
#include <string.h>
#include <stdio.h>
int
main (int c, char **argv) {
char tst[10];
const char hl[] = "hola mundo!";
memset (tst, 0, sizeof(tst));
memcpy (tst, hl, strlen(hl) + 1);
return 0;
}
The memset(3) instruction sets the memory buffer pointed by tst to zero and it is OK. The size of tst is known, and since the operator sizeof is a compile time operator, there is no problem. If tst is a pointer instead of an array of bytes — I mean "char *tst" declaration — we can’t use sizeof and using it is like to get sizeof(NULL) — have noticed that every pointer has the same size?. If we extract the backtrace from the core file, we can reach the line of code which have the error. Depending on the signal, we can reach different kinds of errors, you must read your system documentation to know to what specific error refers it.
other valgrind errors
Depending on your operating system implementation, other errors can occur, if there is no proper memory protection. For example, you can have a conditional jump error and then an invalid write error. If you mix both of them and the system is not capable to protect itself of those errors, valgrind will report them. In other case, the proper signal will be thrown to your process and possibly a core file will be generated, then you must use the traditional debugging techniques to reach the bug.
conclusion
You must consider learning assembly language and computer architecture if you want to work at this level. Nowdays, by C programming most people understand a low level programming. By the years that I was studying, C programming was just another high level language, and I got the basics of programming on it, but just before I was introduced to it by reading some books — about 5 to 7 books on C programming — I was capable to build stronger programs.
Everything is important on C programming, from the basic techniques to complex data structures and algorithms. Most operating systems and user interfaces are made with C as the main programming language and also with C++. The applications of this language are not only applied to low level tasks. There are many programs implementing their algorithms in C and assembler to reach optimal performance, just look at ATLAS, BLAS and some other interesting projects.

