2

I am trying to find a memory leak in a more complicated system with tools like valgrind, gperftools, and libasan. I was getting leaks that I couldn't get rid of so I made the simplest program I could think of.

Why is LeakSanitizer still reporting leaks?

Here is my main file: test.cc

// File: test.cc

int main()
{
    unsigned char* data = new unsigned char[1024];
    delete[] data;
    data = 0;

    return 0;
}

Here is my Makefile:

# Makefile

CC=g++
FLAGS=--std=c++11 -O0 -g -I. -fsanitize=address -fno-omit-frame-pointer -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free
TGT=run
LINK=-static-libasan -ltcmalloc

all: $(TGT)

$(TGT): 
        $(CC) $(FLAGS) test.cc -o $(TGT) $(LINK)

clean:
        rm -f $(TGT)

You can run the program with:

env ASAN_OPTIONS=symbolize=1 ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-3.8 ./run

The analysis is:

=================================================================
==23139==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 8 byte(s) in 1 object(s) allocated from:
    #0 0x481492 in operator new(unsigned long) (/home/user/tests/cpp/leakTest/basic/run+0x481492)
    #1 0x7f46c64bf0dd in InitModule src/malloc_extension.cc:212

SUMMARY: AddressSanitizer: 8 byte(s) leaked in 1 allocation(s).

My system:

user@computer:~$ lsb_release -a
LSB Version:    core-9.20160110ubuntu0.2-amd64:core-9.20160110ubuntu0.2-noarch:security-9.20160110ubuntu0.2-amd64:security-9.20160110ubuntu0.2-noarch
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.3 LTS
Release:    16.04
Codename:   xenial

user@computer:~/$ uname -a
Linux computer 4.4.0-103-generic #126-Ubuntu SMP Mon Dec 4 16:23:28 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

user@computer:~/$ dpkg -l | grep asan
ii  libasan2:amd64                                5.4.0-6ubuntu1~16.04.5                       amd64        AddressSanitizer -- a fast memory error detector

user@computer:~/$ dpkg -l | grep llvm
ii  libllvm3.6v5:amd64                            1:3.6.2-3ubuntu2                             amd64        Modular compiler and toolchain technologies, runtime library
ii  libllvm3.8:amd64                              1:3.8-2ubuntu4                               amd64        Modular compiler and toolchain technologies, runtime library
ii  libllvm4.0:amd64                              1:4.0-1ubuntu1~16.04.2                       amd64        Modular compiler and toolchain technologies, runtime library
ii  llvm                                          1:3.8-33ubuntu3.1                            amd64        Low-Level Virtual Machine (LLVM)
ii  llvm-3.8                                      1:3.8-2ubuntu4                               amd64        Modular compiler and toolchain technologies
ii  llvm-3.8-dev                                  1:3.8-2ubuntu4                               amd64        Modular compiler and toolchain technologies, libraries and headers
ii  llvm-3.8-runtime                              1:3.8-2ubuntu4                               amd64        Modular compiler and toolchain technologies, IR interpreter
ii  llvm-runtime                                  1:3.8-33ubuntu3.1                            amd64        Low-Level Virtual Machine (LLVM), bytecode interpreter

user@computer:~/$ dpkg -l | grep tcmalloc
ii  libtcmalloc-minimal4                          2.4-0ubuntu5.16.04.1                         amd64        efficient thread-caching malloc

user@computer:~/$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 5.4.0-6ubuntu1~16.04.5' --with-bugurl=file:///usr/share/doc/gcc-5/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-5 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-5-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-5-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-5-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.5) 

user@computer:~/$ dpkg -l | grep libstdc
ii  libstdc++-5-dev:amd64                         5.4.0-6ubuntu1~16.04.5                       amd64        GNU Standard C++ Library v3 (development files)
ii  libstdc++6:amd64                              5.4.0-6ubuntu1~16.04.5                       amd64        GNU Standard C++ Library v3

EDIT: Hmm. If I change the linkage of asan in my Makefile from '-static-libasan' to '-lasan', no leaks are reported. Should I not be linking with -static-libasan?

vincent
  • 1,370
  • 2
  • 13
  • 29
  • Any chance that LeakSanitizer depends on the frame pointer? – Justin Randall Dec 21 '17 at 19:12
  • Static linkage should be fine, I would tend to think that the false positive using static linkage is a limitation of the program rather than a problem with the static library. – Justin Randall Dec 21 '17 at 19:13
  • @JustinRandall Removing '-fno-omit-frame-pointer' did not make a difference. What would be the limitation of the program? Sorry I'm not super familiar with this level of debugging. – vincent Dec 21 '17 at 19:15
  • Not all leaks are a problem. Intentionally leaking something is sometimes an optimization - for example during shutdown if you know that running destructors is just pointless work - the OS will clean up anyway after the program exits. So, unless the leak is one that's actually causing memory use to increase pointlessly during runtime, don't get hung-up on it. – Jesper Juhl Dec 21 '17 at 20:18
  • @JesperJuhl That's fair. I'm just trying to understand which leaks I should investigate and which ones I can ignore. I guess I'll let this one go until I can come up with a better example. My actual case involves multi-threading and OpenCV mat objects, which are reference counted. I have a deep hatred for reference counting, since I can't manage the memory. So if a cv::Mat object isn't letting memory go because of a duplicated shared_ptr across memory space (incrementing the counter), it's technically not leaking, which is really difficult (for me) to track down. – vincent Dec 21 '17 at 20:38

1 Answers1

1

I'm not very familiar with tcmalloc, but it seems it is leaking that object intentionally. The pointed to leak:

Direct leak of 8 byte(s) in 1 object(s) allocated from:
    #0 0x481492 in operator new(unsigned long) (/home/user/tests/cpp/leakTest/basic/run+0x481492)
    #1 0x7f46c64bf0dd in InitModule src/malloc_extension.cc:212

Correlates with this source code (github):

static void InitModule() {
  current_instance = new MallocExtension; // reported leak
#ifndef NO_HEAP_CHECK
  HeapLeakChecker::IgnoreObject(current_instance);
#endif
}

It was also reported to be detected by some valgrind versions:
https://github.com/gperftools/gperftools/issues/758

valiano
  • 16,433
  • 7
  • 64
  • 79
  • Hm. Right before this function declaration, current_instance is declared as a static variable. Then the function after InitModule returns the current_instance (as a singleton). Why would that be a leak? – vincent Dec 21 '17 at 20:03
  • @vincent Since that static is dynamically allocated, it is expected to be deallocated before program terminates - I guess there's no distinguishing between statics and non statics in valgrind/asan for that matter. Sometimes it makes sense to avoid deallocating some objects and let the OS free that memory when the program dies (that may be the design choice made for tcmalloc). See this answer, too: https://stackoverflow.com/questions/10572610/memory-leaks-when-using-a-singleton – valiano Dec 22 '17 at 06:53
  • Ok that makes sense. Thanks. – vincent Dec 22 '17 at 16:10