3

I'm trying to fix a memory leak in a very large project. Benchmarks have confirmed that memory leaks are a significant problem, and I'm working on finding the source of them.

Running the project on a very simple case, I get ~850 potential memory leaks reported. All but about 5 of them look like:

==83597== 768 bytes in 3 blocks are possibly lost in loss record 743 of 864
==83597==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==83597==    by 0x548EF93: myproject_malloc (mysourcefile.c:48)
==83597==    by 0x4F13FD5: ??? (in /path/to/project/library-version.so)
==83597==    by 0x1101: ???
==83597==    by 0xF7: ???
==83597==    by 0x64D4D87: ???
==83597==    by 0xFFFFFFFFFFFFFFFD: ???
==83597==    by 0x6: ???
==83597==    by 0x4F03BB0: ??? (in /path/to/project/library-version.so)
==83597==    by 0xFFFFFFFFFFFFFFFD: ???
==83597==    by 0x64D4D87: ???


==83597== 920 bytes in 1 blocks are possibly lost in loss record 750 of 864
==83597==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==83597==    by 0x548EF93: myproject_malloc (mysourcefile.c:48)
==83597==    by 0x4F13FD5: ??? (in /path/to/project/library-version.so)
==83597==    by 0xFFEFFFD5F: ???
==83597==    by 0x38F: ???
==83597==    by 0xFFEFFFE5F: ???
==83597==    by 0xF: ???
==83597==    by 0x54542FF: ??? (in /path/to/project/library-version.so)
==83597==    by 0x4F536CA: ??? (in /path/to/project/library-version.so)
==83597==    by 0x64B981F: ???
==83597==    by 0xF: ???
==83597==    by 0x54542FF: ??? (in /path/to/project/library-version.so)


==83597== 1,360 bytes in 1 blocks are possibly lost in loss record 789 of 864
==83597==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==83597==    by 0x548EF93: myproject_malloc (mysourcefile.c:48)
==83597==    by 0x4F13FD5: ??? (in /path/to/project/library-version.so)
==83597==    by 0x1101: ???
==83597==    by 0x547: ???
==83597==    by 0x1F: ???
==83597==    by 0x6584267: ???
==83597==    by 0x547: ???
==83597==    by 0x4F13808: ??? (in /path/to/project/library-version.so)
==83597==    by 0x6584267: ???
==83597==    by 0x6584527: ???
==83597==    by 0x65805FF: ???

I'm working on getting valgrind to give real, useful output rather than ???, but it may not be possible, and I'd like to be able to get useful output from memcheck until that happens.

A useful report has > 1 function call reported from a real source file rather than a .so file (because every output reports myproject_malloc as a potential leak source). What's the simplest way I can cut out all of the junk from the output and still display it as plain text in my emacs compilation buffer?

I know I could write a Python script that would be a few dozen lines long to do the job by checking every time I enter a new error report and counting the number of times I see ".c:\d+", but I'd prefer something simpler.

Is there a nice way to do this with commandline tools? Or an option to valgrind that I'm not aware of?

Patrick Collins
  • 10,306
  • 5
  • 30
  • 69

2 Answers2

3

Not an option, but a configuration feature: valgrind can be configured with suppression files which tell it to ignore certain stack traces.

For information,

Yes! Use the --gen-suppressions=yes feature to spit out suppressions automatically for you. You can then edit them if you like, eg. combining similar automatically generated suppressions using wildcards like '*'.

If you really want to write suppressions by hand, read the manual carefully. Note particularly that C++ function names must be mangled (that is, not demangled).

Valgrind relies upon symbols. Those ??? are for missing symbols (and some do not appear likely addresses). As I recall it, valgrind supplies those after checking for symbols, so (even if there were a suitable wildcard to work around ? as a meta character), you could not explicitly suppress those.

Your stack traces lack line-numbers for the project library. If you compile with debugging (-g) and design a suitable frame-level suppression, that seems to be an improvement.

Community
  • 1
  • 1
Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
  • I don't want to suppress based on source because there are stack traces that I care about that pass through the problematic library, and stack traces that start there but go into code that has the symbols displayed properly. AFAIK the only way to save all the info is to suppress based on the number of good symbols in the output rather than anything else. Is there a way to do that? – Patrick Collins Dec 17 '15 at 02:33
  • Also, I'm already compiling with `-g` (as far as I can tell, there may be a Makefile in some corner that's not listening to me) --- the reason that I need to do this is because I can't get it to work. – Patrick Collins Dec 17 '15 at 23:31
  • I was referring to the lines with `/path/to/project/library-version.so`. If those had debug-information, a workable suppression file would be better able to reference line-numbers in the library, and use frame-level suppression for the stack items which have question-marks. – Thomas Dickey Dec 17 '15 at 23:35
  • This question is a hack workaround for the fact that I can't get debug information out of the shared library, even though I'm passing the appropriate options to make. (This is a huge project that I'm picking up in the middle, so it's nontrivial to make sure that make is doing what I expect.) – Patrick Collins Dec 17 '15 at 23:36
  • @PatrickCollins Are you sure that the final library isn't being stripped (basically undoing the `-g`)? Should confirm by dumping the symbols in the library or running `file` on the library to see whether it says `not stripped` or `stripped`. – kaylum Dec 18 '15 at 00:01
  • @kaylum It says "not stripped." – Patrick Collins Dec 18 '15 at 18:41
0

As I said in the comments, suppressing based on origin isn't an appropriate technique here because of strange properties of the library I'm working with (almost every stack trace passes through the "bad" library library-version.so and compiling with -g doesn't fix it). I ended up just writing a python script, which I'll post here for future users:

#!/usr/bin/python

import fileinput
import re

START = re.compile("in loss record")
STOP = re.compile("^==\d+== $")
GOOD = re.compile(r"\.c:\d+", re.M)

def main():
  in_line = False
  current = []
  for line in fileinput.input():
    if in_line:
      in_line = not STOP.search(line)
    else:
      in_line = START.search(line)

    if in_line:
      current.append(line)
    else:
      match = GOOD.findall("".join(current))
      if len(match) > 2:
        print "".join(current)
      current = []

if __name__ == "__main__":
  main()

Now you can either save your valgrind output to a file or pipe it directly to this script and it cuts down on the cruft. Adjust the len(match) > FOO line to control how many results you keep.

Patrick Collins
  • 10,306
  • 5
  • 30
  • 69