Using Valgrind's Memcheck tool

Memcheck is Valgrind's default tool; you do not need to pass it explicitly, but can do so with the valgrind --tool=memcheck <program-to-execute with params> syntax.

As a trivial example, let's run Valgrind on the df(1) utility (on an Ubuntu box):

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 17.10
Release: 17.10
Codename: artful
$ df --version |head -n1
df (GNU coreutils) 8.26
$ valgrind df
==1577== Memcheck, a memory error detector
==1577== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1577== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==1577== Command: df
==1577==
Filesystem 1K-blocks Used Available Use% Mounted on
udev 479724 0 479724 0% /dev
tmpfs 100940 10776 90164 11% /run
/dev/sda1 31863632 8535972 21686036 29% /
tmpfs 504692 0 504692 0% /dev/shm
tmpfs 5120 0 5120 0% /run/lock
tmpfs 504692 0 504692 0% /sys/fs/cgroup
tmpfs 100936 0 100936 0% /run/user/1000
==1577==
==1577== HEAP SUMMARY:
==1577== in use at exit: 3,577 bytes in 213 blocks
==1577== total heap usage: 447 allocs, 234 frees, 25,483 bytes allocated
==1577==
==1577== LEAK SUMMARY:
==1577== definitely lost: 0 bytes in 0 blocks
==1577== indirectly lost: 0 bytes in 0 blocks
==1577== possibly lost: 0 bytes in 0 blocks
==1577== still reachable: 3,577 bytes in 213 blocks
==1577== suppressed: 0 bytes in 0 blocks
==1577== Rerun with --leak-check=full to see details of leaked memory
==1577==
==1577== For counts of detected and suppressed errors, rerun with: -v
==1577== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
$

Valgrind literally takes over and runs the df process within it, instrumenting all dynamic memory accesses. It then prints its report. In the preceding code, the lines are prefixed with ==1577==; that's just the PID of the df process.

As no runtime memory bugs were found, no output appears (you will see the difference soon when we run our membugs program under Valgrind's control). In terms of memory leakage, the report states:

definitely lost: 0 bytes in 0 blocks

All these are zero values, so it's fine. If the values under definitely lost were positive, then this would indeed indicate a memory leakage bug that must be further investigated and fixed. The other labels—indirectly/possibly lost, still reachable—are often due to complex or indirect memory handling within the code base (in effect, they are usually false positives one can ignore).

The still reachable usually signifies that, at process exit, some memory blocks were not explicitly freed by the application (but got implicitly freed when the process died). The following statements show this:

  • In use at exit: 3,577 bytes in 213 blocks
  • Total heap usage: 447 allocs, 234 frees, 25,483 bytes

Out of a total of 447 allocs, only 234 frees were done, leaving 447 - 234 = 213 blocks left unfreed.

Okay, now for the interesting bit: let's run our membugs program test cases (from the preceding Chapter 5Linux Memory Issues) under Valgrind and see if it catches the memory bugs that the test cases work hard to provide.

We definitely suggest you look over the previous chapter, and the membugs.c source code, to regain familiarity with the test cases we will be running.

The membugs program has a total of 13 test cases; we shall not attempt to display the output of all of them within the book; we leave it as an exercise to the reader to try running the program with all test cases under Valgrind and deciphering its output report.

It would be of interest to most readers to see the summary table at the end of this section, showing the result of running Valgrind on each of the test cases.

Test case #1: Uninitialized memory access

$ ./membugs 1
true: x=32568
$
For readability, we remove parts of the output shown as follows and truncate the program pathname.

Now under Valgrind's control:

$ valgrind ./membugs 1
==19549== Memcheck, a memory error detector
==19549== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==19549== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==19549== Command: ./membugs 1
==19549==
==19549== Conditional jump or move depends on uninitialised value(s)
==19549== at 0x40132C: uninit_var (in <...>/ch3/membugs)
==19549== by 0x401451: process_args (in <...>/ch3/membugs)
==19549== by 0x401574: main (in <...>/ch3/membugs)
==19549==

[...]

==19549== Conditional jump or move depends on uninitialised value(s)
==19549== at 0x4E9101C: vfprintf (in /usr/lib64/libc-2.26.so)
==19549== by 0x4E99255: printf (in /usr/lib64/libc-2.26.so)
==19549== by 0x401357: uninit_var (in <...>/ch3/membugs)
==19549== by 0x401451: process_args (in <...>/ch3/membugs)
==19549== by 0x401574: main (in <...>/ch3/membugs)
==19549==
false: x=0
==19549==
==19549== HEAP SUMMARY:
==19549== in use at exit: 0 bytes in 0 blocks
==19549== total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==19549==
==19549== All heap blocks were freed -- no leaks are possible
==19549==
==19549== For counts of detected and suppressed errors, rerun with: -v
==19549== Use --track-origins=yes to see where uninitialised values come from
==19549== ERROR SUMMARY: 6 errors from 6 contexts (suppressed: 0 from 0)
$

Clearly, Valgrind has caught the uninitialized memory access bug! The text highlighted in bold clearly reveals the case.

However, notice that though Valgrind can show us the call stack—including the process pathname—it seems to be unable to show us the line number in the source code where the offending bug is present. Hang on, though. We can achieve precisely this by running Valgrind with the debug-enabled version of the program:

$ make membugs_dbg
gcc -g -ggdb -gdwarf-4 -O0 -Wall -Wextra -c membugs.c -o membugs_dbg.o

[...]

membugs.c: In function ‘uninit_var’:
membugs.c:283:5: warning: ‘x’ is used uninitialized in this function [-Wuninitialized]
if (x > MAXVAL)
^

[...]

gcc -g -ggdb -gdwarf-4 -O0 -Wall -Wextra -c ../common.c -o common_dbg.o
gcc -o membugs_dbg membugs_dbg.o common_dbg.o

[...]
Common GCC flags used for debugging

See the gcc(1) man page for details. Briefly:
-g: Produce sufficient debugging information such that a tool such as the GNU Debugger (GDB) has to debug symbolic information to work with (modern Linux would typically use the DWARF format).
-ggdb: Use the most expressive format possible for the OS.
-gdwarf-4: Debug info is in the DWARF-<version> format (ver. 4 is appropriate).
-O0 : Optimization level 0; good for debugging.

In the following code, we retry running Valgrind with the debug-enabled version of our binary executable, membugs_dbg:

$ valgrind --tool=memcheck ./membugs_dbg 1
==20079== Memcheck, a memory error detector
==20079== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==20079== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==20079== Command: ./membugs_dbg 1
==20079==
==20079== Conditional jump or move depends on uninitialised value(s)
==20079== at 0x40132C: uninit_var (membugs.c:283)
==20079== by 0x401451: process_args (membugs.c:326)
==20079== by 0x401574: main (membugs.c:379)
==20079==
==20079== Conditional jump or move depends on uninitialised value(s)
==20079== at 0x4E90DAA: vfprintf (in /usr/lib64/libc-2.26.so)
==20079== by 0x4E99255: printf (in /usr/lib64/libc-2.26.so)
==20079== by 0x401357: uninit_var (membugs.c:286)
==20079== by 0x401451: process_args (membugs.c:326)
==20079== by 0x401574: main (membugs.c:379)
==20079==
==20079== Use of uninitialised value of size 8
==20079== at 0x4E8CD7B: _itoa_word (in /usr/lib64/libc-2.26.so)
==20079== by 0x4E9043D: vfprintf (in /usr/lib64/libc-2.26.so)
==20079== by 0x4E99255: printf (in /usr/lib64/libc-2.26.so)
==20079== by 0x401357: uninit_var (membugs.c:286)
==20079== by 0x401451: process_args (membugs.c:326)
==20079== by 0x401574: main (membugs.c:379)

[...]

==20079==
false: x=0
==20079==
==20079== HEAP SUMMARY:
==20079== in use at exit: 0 bytes in 0 blocks
==20079== total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==20079==
==20079== All heap blocks were freed -- no leaks are possible
==20079==
==20079== For counts of detected and suppressed errors, rerun with: -v
==20079== Use --track-origins=yes to see where uninitialised values come from
==20079== ERROR SUMMARY: 6 errors from 6 contexts (suppressed: 0 from 0)
$

As usual, read the call stack in a bottom-up fashion and it will make sense!

Important: Please note that, unfortunately, it's quite possible that the precise line numbers shown in the output as follows may not precisely match the line number in the latest version of the source file in the book's GitHub repository.

Here is the the source code (the nl utility is used here to show the code with all lines numbered):

$  nl --body-numbering=a membugs.c 

[...]

278 /* option = 1 : uninitialized var test case */
279 static void uninit_var()
280 {
281 int x;
282
283 if (x)
284 printf("true case: x=%d ", x);
285 else
286 printf("false case ");
287 }

[...]

325 case 1:
326 uninit_var();
327 break;

[...]

377 int main(int argc, char **argv)
378 {
379 process_args(argc, argv);
380 exit(EXIT_SUCCESS);
381 }

We can now see that Valgrind has indeed perfectly captured the buggy case.

Test case #5: read overflow on compile-time memory:

$ valgrind ./membugs_dbg 5
==23024== Memcheck, a memory error detector
==23024== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==23024== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==23024== Command: ./membugs_dbg 5
==23024==
arr = aaaaa����
==23024==
==23024== HEAP SUMMARY:
==23024== in use at exit: 0 bytes in 0 blocks
==23024== total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==23024==
==23024== All heap blocks were freed -- no leaks are possible
==23024==
==23024== For counts of detected and suppressed errors, rerun with: -v
==23024== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
$

Would you look at that!? Valgrind fails to catch the read overflow memory bug. Why? It's a limitation: Valgrind can only instrument, and therefore catch, UB (bugs) on dynamically allocated memory. The preceding test case used static compile-time allocated memory.

So, let's try the same test, but this time using dynamically allocated memory; that's precisely what test case #6 is designed to do.

Test case #6: read overflow on dynamic memory (for readability, we truncated some of the output):

$ ./membugs_dbg 2>&1 |grep 6
option = 6 : out-of-bounds : read overflow [on dynamic memory]
$ valgrind ./membugs_dbg 6
[...]
==23274== Command: ./membugs_dbg 6
==23274==
==23274== Invalid write of size 1
==23274== at 0x401127: read_overflow_dynmem (membugs.c:215)
==23274== by 0x401483: process_args (membugs.c:341)
==23274== by 0x401574: main (membugs.c:379)
==23274== Address 0x521f045 is 0 bytes after a block of size 5 alloc'd
==23274== at 0x4C2FB6B: malloc (vg_replace_malloc.c:299)
==23274== by 0x4010D9: read_overflow_dynmem (membugs.c:205)
==23274== by 0x401483: process_args (membugs.c:341)
==23274== by 0x401574: main (membugs.c:379)
[...]
==23274== Invalid write of size 1
==23274== at 0x40115E: read_overflow_dynmem (membugs.c:216)
==23274== by 0x401483: process_args (membugs.c:341)
==23274== by 0x401574: main (membugs.c:379)
==23274== Address 0x521f04a is 5 bytes after a block of size 5 alloc'd
==23274== at 0x4C2FB6B: malloc (vg_replace_malloc.c:299)
==23274== by 0x4010D9: read_overflow_dynmem (membugs.c:205)
==23274== by 0x401483: process_args (membugs.c:341)
==23274== by 0x401574: main (membugs.c:379)
==23274==
==23274== Invalid read of size 1
==23274== at 0x4C32B94: strlen (vg_replace_strmem.c:458)
==23274== by 0x4E91955: vfprintf (in /usr/lib64/libc-2.26.so)
==23274== by 0x4E99255: printf (in /usr/lib64/libc-2.26.so)
==23274== by 0x401176: read_overflow_dynmem (membugs.c:217)
==23274== by 0x401483: process_args (membugs.c:341)
==23274== by 0x401574: main (membugs.c:379)
==23274== Address 0x521f045 is 0 bytes after a block of size 5 alloc'd
==23274== at 0x4C2FB6B: malloc (vg_replace_malloc.c:299)
==23274== by 0x4010D9: read_overflow_dynmem (membugs.c:205)
==23274== by 0x401483: process_args (membugs.c:341)
==23274== by 0x401574: main (membugs.c:379)
[...]
arr = aaaaaSecreT
==23274== Conditional jump or move depends on uninitialised value(s)
==23274== at 0x4E90DAA: vfprintf (in /usr/lib64/libc-2.26.so)
==23274== by 0x4E99255: printf (in /usr/lib64/libc-2.26.so)
==23274== by 0x401195: read_overflow_dynmem (membugs.c:220)
==23274== by 0x401483: process_args (membugs.c:341)
==23274== by 0x401574: main (membugs.c:379)
==23274==
==23274== Use of uninitialised value of size 8
==23274== at 0x4E8CD7B: _itoa_word (in /usr/lib64/libc-2.26.so)
==23274== by 0x4E9043D: vfprintf (in /usr/lib64/libc-2.26.so)
==23274== by 0x4E99255: printf (in /usr/lib64/libc-2.26.so)
==23274== by 0x401195: read_overflow_dynmem (membugs.c:220)
==23274== by 0x401483: process_args (membugs.c:341)
==23274== by 0x401574: main (membugs.c:379)
[...]
==23274== ERROR SUMMARY: 31 errors from 17 contexts (suppressed: 0 from 0)
$

Well, this time, plenty of errors were caught with precise call stack locations revealing the exact point in the source (as we have compiled with -g).

Test case #8: UAF (use-after-free):

$ ./membugs_dbg 2>&1 |grep 8
option = 8 : UAF (use-after-free) test case
$
(partial) screenshot of the action when Valgrind catches the UAF bugs

Valgrind does catch the UAF!

Test case #8: UAR (use-after-return):

$ ./membugs_dbg 9
res: (null)
$ valgrind ./membugs_dbg 9
==7594== Memcheck, a memory error detector
==7594== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==7594== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==7594== Command: ./membugs_dbg 9
==7594==
res: (null)
==7594==
==7594== HEAP SUMMARY:
==7594== in use at exit: 0 bytes in 0 blocks
==7594== total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==7594==
==7594== All heap blocks were freed -- no leaks are possible
==7594==
==7594== For counts of detected and suppressed errors, rerun with: -v
==7594== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
$

Whoops! Valgrind does not catch the UAR bug!

Test Case #13: Memory leak case #3—lib API leak. We run the memory leak test case #3 by selecting 13 as the parameter to membugs. It's useful to note that only when run with the --leak-check=full option does Valgrind display the origin of the leak (via the displayed call stack):

$ valgrind --leak-resolution=high --num-callers=50 
--leak-check=full ./membugs_dbg 13
==22849== Memcheck, a memory error detector
==22849== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==22849== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==22849== Command: ./membugs_dbg 13
==22849==

## Leakage test: case 3: "lib" API: runtime cond = 0
mypath = /usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/sbin:/usr/sbin:/usr/local/sbin:/home/kai/MentorGraphics/Sourcery_CodeBench_Lite_for_ARM_GNU_Linux/bin/:/mnt/big/scratchpad/buildroot-2017.08.1/output/host/bin/:/sbin:/usr/sbin:/usr/local/sbin

## Leakage test: case 3: "lib" API: runtime cond = 1
mypath = /usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/sbin:/usr/sbin:/usr/local/sbin:/home/kai/MentorGraphics/Sourcery_CodeBench_Lite_for_ARM_GNU_Linux/bin/:/mnt/big/scratchpad/buildroot-2017.08.1/output/host/bin/:/sbin:/usr/sbin:/usr/local/sbin
==22849==
==22849== HEAP SUMMARY:
==22849== in use at exit: 4,096 bytes in 1 blocks
==22849== total heap usage: 3 allocs, 2 frees, 9,216 bytes allocated
==22849==
==22849== 4,096 bytes in 1 blocks are definitely lost in loss record 1 of 1
==22849== at 0x4C2FB6B: malloc (vg_replace_malloc.c:299)
==22849== by 0x400A0C: silly_getpath (membugs.c:38)
==22849== by 0x400AC6: leakage_case3 (membugs.c:59)
==22849== by 0x40152B: process_args (membugs.c:367)
==22849== by 0x401574: main (membugs.c:379)
==22849==
==22849== LEAK SUMMARY:
==22849== definitely lost: 4,096 bytes in 1 blocks
==22849== indirectly lost: 0 bytes in 0 blocks
==22849== possibly lost: 0 bytes in 0 blocks
==22849== still reachable: 0 bytes in 0 blocks
==22849== suppressed: 0 bytes in 0 blocks
==22849==
==22849== For counts of detected and suppressed errors, rerun with: -v
==22849== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
$

The Valgrind man page recommends setting --leak-resolution=high and --num-callers=  to 40 or higher.

The man page on valgrind(1) covers the many options it provides (such as logging and tool (Memcheck) options); take a look to gain a deeper understanding of this tool's usage.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.105.193