Address Sanitizer can not detect memory leaks with option -O

5.8k views Asked by At

When I use Address Sanitizer(clang v3.4) to detect memory leak, I found that using -O(except -O0) option would always lead to a no-leak-detected result.

The code is simple:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    int* array = (int *)malloc(sizeof(int) * 100);
    for (int i = 0; i < 100; i++) //Initialize
    array[i] = 0;
    return 0;
} 

when compile with -O0,

clang -fsanitize=address -g -O0 main.cpp

it will detect memory correctly,

==2978==WARNING: Trying to symbolize code, but external symbolizer is not initialized!

=================================================================
==2978==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 400 byte(s) in 1 object(s) allocated from:
    #0 0x4652f9 (/home/mrkikokiko/sdk/MemoryCheck/a.out+0x4652f9)
    #1 0x47b612 (/home/mrkikokiko/sdk/MemoryCheck/a.out+0x47b612)
    #2 0x7fce3603af44 (/lib/x86_64-linux-gnu/libc.so.6+0x21f44)

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

however, when -O added,

clang -fsanitize=address -g -O main.cpp

nothing is detected! And I find nothing about it in official document.

2

There are 2 answers

1
Vittorio Romeo On BEST ANSWER

This is because your code is completely optimized away. The resulting assembly is something like:

main:                                   # @main
    xorl    %eax, %eax
    retq

Without any call to malloc, there is no memory allocation... and therefore no memory leak.


In order to to have AddressSanitizer detect the memory leak, you can either:

  • Compile with optimizations disabled, as Simon Kraemer mentioned in the comments.

  • Mark array as volatile, preventing the optimization:

     main:                                   # @main
            pushq   %rax
            movl    $400, %edi              # imm = 0x190
            callq   malloc                  # <<<<<< call to malloc
            movl    $9, %ecx
    .LBB0_1:                                # =>This Inner Loop Header: Depth=1
            movl    $0, -36(%rax,%rcx,4)
            movl    $0, -32(%rax,%rcx,4)
            movl    $0, -28(%rax,%rcx,4)
            movl    $0, -24(%rax,%rcx,4)
            movl    $0, -20(%rax,%rcx,4)
            movl    $0, -16(%rax,%rcx,4)
            movl    $0, -12(%rax,%rcx,4)
            movl    $0, -8(%rax,%rcx,4)
            movl    $0, -4(%rax,%rcx,4)
            movl    $0, (%rax,%rcx,4)
            addq    $10, %rcx
            cmpq    $109, %rcx
            jne     .LBB0_1
            xorl    %eax, %eax
            popq    %rcx
            retq
    
0
Basile Starynkevitch On

Look into the generated code.

Both GCC & Clang actually know about the semantics of malloc. Because on my Linux/Debian system <stdlib.h> contains

extern void *malloc (size_t __size) __THROW __attribute_malloc__ __wur;

and the __attribute_malloc__ & _wur (and __THROW) are macros defined elsewhere. Read about Common Function Attributes in GCC documentation, and Clang documentation says:

Clang aims to support a broad range of GCC extensions.

I strongly suspect that with -O the call to malloc is optimized by removing it.

On my Linux/x86-64 machine using clang -O -S psbshdk.c (with clang 3.8) I am indeed getting:

    .globl  main
    .align  16, 0x90
    .type   main,@function
 main:                                   # @main
    .cfi_startproc
 # BB#0:
    xorl    %eax, %eax
    retq
 .Lfunc_end0:
    .size   main, .Lfunc_end0-main
    .cfi_endproc

The address sanitizer is working on the emitted binary (which won't contain any malloc call).

BTW, you could compile with clang -O -g then use valgrind, or compile with clang -O -fsanitize=address -g. Both clang & gcc are able to optimize and give some debug information (which might be "approximate" when optimizing a lot).