Tag Archives: Valgrind

How to detect unclosed file handles in C/C++

Sometimes we forget to close file handles which are no longer required in the program. This might eventually eat up some useful resources like open file count, memory, etc. Valgrind does magic at checking a variety of such leakages. All you need is run the program under Valgrind with –track-fds option. Here is the output of a simple C program, executed under Valgrind, which opens a file handle but doesn’t close it.

$ gcc -Wall -g -o fopen fopen.c
$ valgrind --tool=memcheck --leak-check=full --track-fds=yes ./fopen
==2771== Memcheck, a memory error detector
==2771== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==2771== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==2771== Command: ./fopen
==2771== 
File opened successfully
==2771== 
==2771== FILE DESCRIPTORS: 4 open at exit.
==2771== Open file descriptor 3: /tmp/fl
==2771==    at 0x4125F73: __open_nocancel (syscall-template.S:82)
==2771==    by 0x40B9ACB: _IO_file_open (fileops.c:233)
==2771==    by 0x40B9C87: _IO_file_fopen@@GLIBC_2.1 (fileops.c:338)
==2771==    by 0x40ADD86: __fopen_internal (iofopen.c:93)
==2771==    by 0x80484F7: main (fopen.c:23)
==2771== 
==2771== Open file descriptor 2: /dev/pts/0
==2771==    
==2771== 
==2771== Open file descriptor 1: /dev/pts/0
==2771==    
==2771== 
==2771== Open file descriptor 0: /dev/pts/0
==2771==    
==2771== 
==2771== 
==2771== HEAP SUMMARY:
==2771==     in use at exit: 352 bytes in 1 blocks
==2771==   total heap usage: 1 allocs, 0 frees, 352 bytes allocated
==2771== 
==2771== LEAK SUMMARY:
==2771==    definitely lost: 0 bytes in 0 blocks
==2771==    indirectly lost: 0 bytes in 0 blocks
==2771==      possibly lost: 0 bytes in 0 blocks
==2771==    still reachable: 352 bytes in 1 blocks
==2771==         suppressed: 0 bytes in 0 blocks
==2771== Reachable blocks (those to which a pointer was found) are not shown.
==2771== To see them, rerun with: --leak-check=full --show-reachable=yes
==2771== 
==2771== For counts of detected and suppressed errors, rerun with: -v
==2771== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

As you can see, at the end Valgrind prints a list of all open file descriptors and stack trace showing where the file was opened.

Using Valgrind with GDB

Valgrind is a very powerful tool for detecting & diagnosing memory related issues in a C/C++ program. Sometimes, the task of locating the actual problematic area becomes increasingly tedious if the code base is huge and complex.

Let us now try to debug a small program running under Valgrind using GDB.

/* leaky.c */
#include
#include

int main()
{
  char *ptr= malloc(100);
  printf("I am a leaky program!\n");
  return 0;
}

Before we move further, one point to note here is that a process running under Valgrind is actually running on a synthetic CPU provided by Valgrind and hence it cannot be debugged directly using GDB [source]. So, in order to overcome this limitation, Valgrind-3.7.0 introduced ‘gdbserver’, an implementation of GDB remote debugging protocol, using which the process can be debugged.

Step 1 : Start the process under Valgrind with –vgdb=yes & –vgdb-error=0 options

> gcc -g leaky.c

> valgrind --vgdb=yes --vgdb-error=0 ./a.out 
==6148== Memcheck, a memory error detector
==6148== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==6148== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==6148== Command: ./a.out
==6148== 
==6148== (action at startup) vgdb me ... 
==6148== 
==6148== TO DEBUG THIS PROCESS USING GDB: start GDB like this
==6148==   /path/to/gdb ./a.out
==6148== and then give GDB the following command
==6148==   target remote | /usr/lib64/valgrind/../../bin/vgdb --pid=6148
==6148== --pid is optional if only one valgrind process is running
==6148==

Step 2 : In a different terminal start GDB

> gdb a.out 
GNU gdb Red Hat Linux (6.5-25.el5rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu"...Using host libthread_db library "/lib64/libthread_db.so.1".

Step 3 : In the same gdb session,

(gdb) target remote | /usr/lib64/valgrind/../../bin/vgdb
Remote debugging using | /usr/lib64/valgrind/../../bin/vgdb
relaying data between gdb and process 6148
[New thread 6148]
[Switching to thread 6148]
0x0000003f10000a60 in ?? ()
(gdb) b main
Breakpoint 1 at 0x4004e0: file leaky.c, line 7.
(gdb) c
Continuing.

Breakpoint 1, main () at leaky.c:7
7	  char *ptr= malloc(100);
(gdb)

And step through the code further..