Programming Problems and Debugging 111
7.2.3 Detect Invalid Memory Accesses
Another way to detect invalid memory accesses is using valgrind. The log file from
valgrind can be quite long. You should go to the very last line. In this example, the line
says:
ERROR SUMMARY: 59745 errors from 4 contexts (suppressed: 2 from 2)
Do not be too concerned about the number of errors detected. Fixing one error will Often
fix many of the other detected errors. This is because a single error can be hit many times
as a program executes. Go to the very top of the log file and start looking for anything
related to the source files, i.e., mystring.c, mystring.h, and main.c. The first detected
problem related to mystring.c is:
==4238== Conditional jump or move depends on uninitialised value(s)
==4238== at 0x4008D2: my_countchar (mystring2.c:17)
==4238== by 0x400CC8: main (main.c:74)
Another detected problem is:
==4238== Conditional jump or move depends on uninitialised value(s)
==4238== at 0x4008BE: my_countchar (mystring2.c:19)
==4238== by 0x400CC8: main (main.c:74)
This is related to the problem at line 17. If we fix line 17, the problem at line 19
disappears. Some students think that accessing invalid memory is harmless as long as the
programs do not have segmentation faults. This is wrong. Allowing invalid addresses is
one of the most common security problems in software. It can allow a malicious program to
“hijack” another program. If a program accesses invalid addresses, the program’s behavior is
not defined. That means it may work a hundred or a thousand times, and then mysteriously
fail. The same program may fail when using a different compiler, or run on a different
computer.
Please remember that testing can demonstrate that something is wrong; however, testing
cannot demonstrate that everything is right. As we see, Linux stops the program when str
is already very far away from valid addresses. Thus, we cannot rely on testing exclusively.
Instead, we need to use many methods to prevent and detect mistakes.
“Which one should I use, gdb or valgrind?”, you may ask. The answer is both. These
two tools serve different purposes. Choosing gdb vs. valgrind is like choosing a hammer vs.
a screw driver. Use the right tool for the job: gdb is interactive, and allows a programmer
to see the program’s execution line by line. In contrast, valgrind runs the program until it
stops (or crashes). In general it is a good idea to use valgrind first to detect whether there
are any memory problems and then use gdb to pinpoint the problem. You should always use
valgrind to check whether your programs have invalid memory accesses. The command is:
$ valgrind –leak-check=full –tool=memcheck –verbose
What is the difference between gcc and valgrind? Doesn’t gcc also check whether a
program has problems? The gcc compiler checks the source code: i.e., it finds syntax errors.
This is a very rudimentary form of error checking. It is like a spell-checker in a document
editor. An article without any spelling error does not mean that the article makes any sense.
In similar fashion, gcc does not check what happens when the program runs. It is impossible
for gcc to check what the program does when it is running.
In contrast, valgrind is a run-time checker. The program must be run for valgrind to
check anything. This implies the following: If the program does not execute the parts of code