google/sanitizers

Suppress leak checking on exit

Opened this issue · 15 comments

I am working on a software where the developers think that it is OK to not free memory still reacheable on exit. I am only interested in leaks while the program is running.
Is there a way to suppress leak checking on exit?

Do you mean suppressing all leaks? If so, use LSAN_OPTIONS=detect_leaks=0. If you want to suppress only some particular leaks, use LSAN_OPTIONS=suppressions= interface.

No, I do not want to suppress all leaks. I just want to suppress leaks of memory still reachable on exit. But, I still want to see leaks of memory that are not reachable anymore.

Hm, LSan shouldn't report reachable memory blocks, it reports only not reachable ones.

LSan reports reachable memory blocks on exit.
See:
#include <stdlib.h>
int
main (int argc, char *argv[])
{
void *a;
a = malloc(1);
return 0;
}

==17315==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 1 byte(s) in 1 object(s) allocated from:
#0 0x7f4ecb8ebe38 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.3+0xc1e38)
#1 0x40071e in main memleak.c:7
#2 0x7f4ecb4a96ff in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x206ff)

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

yugr commented

Given that you don't use a, compiler might have removed it from main's stack...

The program:
#include <stdlib.h>
int
main (int argc, char *argv[])
{
char *a;
a = malloc(1);
*a = 'a';
return 0;
}

Compiled using:
gcc -g -fsanitize=address -fno-omit-frame-pointer -Wall -o memleak memleak.c

still shows reachable leaks on exit.

I have seen that if I call exit(0) reachable leaks are not reported anymore.
But, how may I do the same if the program does not call exit() ?

It seems that a is out of scope when LSan performs analysis. Could you:

  1. add printf("%p\n", &a);
  2. run your testcase with LSAN_OPTIONS=log_pointers=1
    ?

This is the new output:
0x7ffec5325f10
==1562==Scanning GLOBAL range 0x000000601000-0x000000601300.
==1562==Scanning GLOBAL range 0x7fa07e169718-0x7fa07e16ed60.
==1562==Scanning GLOBAL range 0x7fa07e36efe8-0x7fa07ede0d90.
==1562==Scanning GLOBAL range 0x7fa07de3c7c8-0x7fa07de459a0.
==1562==0x7fa07de41628: found 0x61900000b480 pointing into chunk 0x61900000b480-0x61900000b880 of size 1024.
==1562==Scanning GLOBAL range 0x7fa07daa3d60-0x7fa07daa40f0.
==1562==Scanning GLOBAL range 0x7fa07d89fd58-0x7fa07d8a0bc0.
==1562==Scanning GLOBAL range 0x7fa07d693b78-0x7fa07d698428.
==1562==Scanning GLOBAL range 0x7fa07d46c338-0x7fa07d47b260.
==1562==0x7fa07d477be8: found 0x631000000800 pointing into chunk 0x631000000800-0x631000012400 of size 72704.
==1562==Scanning GLOBAL range 0x7fa07d0f9d70-0x7fa07d0fa0e8.
==1562==Scanning GLOBAL range 0x7fa07cdf52d0-0x7fa07cdf5950.
==1562==Scanning GLOBAL range 0x7fa07f004bc0-0x7fa07f006168.
==1562==Scanning REGISTERS range 0x7fa07efaf000-0x7fa07efaf0d8.
==1562==Scanning STACK range 0x7ffec5325bc8-0x7ffec5328000.
==1562==Scanning TLS range 0x7fa07efc1000-0x7fa07efc2080.
==1562==Scanning HEAP range 0x631000000800-0x631000012400.
==1562==Scanning HEAP range 0x61900000b480-0x61900000b880.
==1562==Processing platform-specific allocations.
==1562==Scanning leaked chunks.
==1562==Scanning HEAP range 0x60200000eff0-0x60200000eff1.

==1554==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 1 byte(s) in 1 object(s) allocated from:
#0 0x7fa07df07e38 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.3+0xc1e38)
#1 0x4009a6 in main memleak.c:8
#2 0x7fa07dac56ff in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x206ff)

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

for:
#include <stdlib.h>
#include <stdio.h>
int
main (int argc, char *argv[])
{
char *a;
a = malloc(1);
*a = 'a';
printf("%p\n", &a);
return 0;
}

yugr commented

I think when you call exit(), main's stack frame is still active so LSan can find reference to allocated block there whereas in alternative case the frame is already destroyed and so not considered.

On Mon, Sep 5, 2016 at 12:57 PM, jmithmstr notifications@github.com wrote:

This is the new output:
0x7ffec5325f10
==1562==Scanning GLOBAL range 0x000000601000-0x000000601300.
==1562==Scanning GLOBAL range 0x7fa07e169718-0x7fa07e16ed60.
==1562==Scanning GLOBAL range 0x7fa07e36efe8-0x7fa07ede0d90.
==1562==Scanning GLOBAL range 0x7fa07de3c7c8-0x7fa07de459a0.
==1562==0x7fa07de41628: found 0x61900000b480 pointing into chunk
0x61900000b480-0x61900000b880 of size 1024.
==1562==Scanning GLOBAL range 0x7fa07daa3d60-0x7fa07daa40f0.
==1562==Scanning GLOBAL range 0x7fa07d89fd58-0x7fa07d8a0bc0.
==1562==Scanning GLOBAL range 0x7fa07d693b78-0x7fa07d698428.
==1562==Scanning GLOBAL range 0x7fa07d46c338-0x7fa07d47b260.
==1562==0x7fa07d477be8: found 0x631000000800 pointing into chunk
0x631000000800-0x631000012400 of size 72704.
==1562==Scanning GLOBAL range 0x7fa07d0f9d70-0x7fa07d0fa0e8.
==1562==Scanning GLOBAL range 0x7fa07cdf52d0-0x7fa07cdf5950.
==1562==Scanning GLOBAL range 0x7fa07f004bc0-0x7fa07f006168.
==1562==Scanning REGISTERS range 0x7fa07efaf000-0x7fa07efaf0d8.
==1562==Scanning STACK range 0x7ffec5325bc8-0x7ffec5328000.
==1562==Scanning TLS range 0x7fa07efc1000-0x7fa07efc2080.
==1562==Scanning HEAP range 0x631000000800-0x631000012400.
==1562==Scanning HEAP range 0x61900000b480-0x61900000b880.
==1562==Processing platform-specific allocations.
==1562==Scanning leaked chunks.
==1562==Scanning HEAP range 0x60200000eff0-0x60200000eff1.

==1554==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 1 byte(s) in 1 object(s) allocated from:
#0 0x7fa07df07e38 in malloc (/usr/lib/x86_64-linux-gnu/
libasan.so.3+0xc1e38)
#1 #1 0x4009a6 in main
memleak.c:8
#2 #2 0x7fa07dac56ff in
__libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x206ff)

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

for:
#include
#include
int
main (int argc, char *argv[])
{
char *a;
a = malloc(1);
*a = 'a';
printf("%p\n", &a);
return 0;
}

Try to manually execute __lsan_do_leak_check() at the point where you are
still interested in leaks and then __lsan_disable() to disable at exit
checking.

OK, the code works as expected, and there is no need for any enhancement.

Maybe the documentation could be improved to document this use case.

WAI

Maybe we still should consider to always suppress leak checks for exit()?
Different levels of optimization may hide some stack pointers from the lsan and make reports inconsistent.

My concern is that maybe some users already expect lsan even for exit() and if we change that we will brake them.
If we don't want to change this behavior then we need to describe this in documentation.
@kcc WDYT?

kcc commented

yea, that sounds like breaking an existing useful functionality...

Different levels of optimization may hide some stack pointers from the lsan and make reports inconsistent.

Do we understand how it happens?
Don't we look in the registers for roots?

Do we understand how it happens?
No. I can't reproduce myself. It's a very specific binary.

Don't we look in the registers for roots?
We do. But I recently discovered a bug and we may look not enough https://reviews.llvm.org/D87754