Home | History | Annotate | Download | only in tests
      1 #!/usr/bin/env perl
      2 #***************************************************************************
      3 #                                  _   _ ____  _
      4 #  Project                     ___| | | |  _ \| |
      5 #                             / __| | | | |_) | |
      6 #                            | (__| |_| |  _ <| |___
      7 #                             \___|\___/|_| \_\_____|
      8 #
      9 # Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel (at] haxx.se>, et al.
     10 #
     11 # This software is licensed as described in the file COPYING, which
     12 # you should have received as part of this distribution. The terms
     13 # are also available at https://curl.haxx.se/docs/copyright.html.
     14 #
     15 # You may opt to use, copy, modify, merge, publish, distribute and/or sell
     16 # copies of the Software, and permit persons to whom the Software is
     17 # furnished to do so, under the terms of the COPYING file.
     18 #
     19 # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     20 # KIND, either express or implied.
     21 #
     22 ###########################################################################
     23 #
     24 # Example input:
     25 #
     26 # MEM mprintf.c:1094 malloc(32) = e5718
     27 # MEM mprintf.c:1103 realloc(e5718, 64) = e6118
     28 # MEM sendf.c:232 free(f6520)
     29 
     30 my $mallocs=0;
     31 my $callocs=0;
     32 my $reallocs=0;
     33 my $strdups=0;
     34 my $wcsdups=0;
     35 my $showlimit;
     36 
     37 while(1) {
     38     if($ARGV[0] eq "-v") {
     39         $verbose=1;
     40         shift @ARGV;
     41     }
     42     elsif($ARGV[0] eq "-t") {
     43         $trace=1;
     44         shift @ARGV;
     45     }
     46     elsif($ARGV[0] eq "-l") {
     47         # only show what alloc that caused a memlimit failure
     48         $showlimit=1;
     49         shift @ARGV;
     50     }
     51     else {
     52         last;
     53     }
     54 }
     55 
     56 my $maxmem;
     57 
     58 sub newtotal {
     59     my ($newtot)=@_;
     60     # count a max here
     61 
     62     if($newtot > $maxmem) {
     63         $maxmem= $newtot;
     64     }
     65 }
     66 
     67 my $file = $ARGV[0];
     68 
     69 if(! -f $file) {
     70     print "Usage: memanalyze.pl [options] <dump file>\n",
     71     "Options:\n",
     72     " -l  memlimit failure displayed\n",
     73     " -v  Verbose\n",
     74     " -t  Trace\n";
     75     exit;
     76 }
     77 
     78 open(FILE, "<$file");
     79 
     80 if($showlimit) {
     81     while(<FILE>) {
     82         if(/^LIMIT.*memlimit$/) {
     83             print $_;
     84             last;
     85         }
     86     }
     87     close(FILE);
     88     exit;
     89 }
     90 
     91 
     92 my $lnum=0;
     93 while(<FILE>) {
     94     chomp $_;
     95     $line = $_;
     96     $lnum++;
     97     if($line =~ /^LIMIT ([^ ]*):(\d*) (.*)/) {
     98         # new memory limit test prefix
     99         my $i = $3;
    100         my ($source, $linenum) = ($1, $2);
    101         if($trace && ($i =~ /([^ ]*) reached memlimit/)) {
    102             print "LIMIT: $1 returned error at $source:$linenum\n";
    103         }
    104     }
    105     elsif($line =~ /^MEM ([^ ]*):(\d*) (.*)/) {
    106         # generic match for the filename+linenumber
    107         $source = $1;
    108         $linenum = $2;
    109         $function = $3;
    110 
    111         if($function =~ /free\((\(nil\)|0x([0-9a-f]*))/) {
    112             $addr = $2;
    113             if($1 eq "(nil)") {
    114                 ; # do nothing when free(NULL)
    115             }
    116             elsif(!exists $sizeataddr{$addr}) {
    117                 print "FREE ERROR: No memory allocated: $line\n";
    118             }
    119             elsif(-1 == $sizeataddr{$addr}) {
    120                 print "FREE ERROR: Memory freed twice: $line\n";
    121                 print "FREE ERROR: Previously freed at: ".$getmem{$addr}."\n";
    122             }
    123             else {
    124                 $totalmem -= $sizeataddr{$addr};
    125                 if($trace) {
    126                     print "FREE: malloc at ".$getmem{$addr}." is freed again at $source:$linenum\n";
    127                     printf("FREE: %d bytes freed, left allocated: $totalmem bytes\n", $sizeataddr{$addr});
    128                 }
    129 
    130                 newtotal($totalmem);
    131                 $frees++;
    132 
    133                 $sizeataddr{$addr}=-1; # set -1 to mark as freed
    134                 $getmem{$addr}="$source:$linenum";
    135 
    136             }
    137         }
    138         elsif($function =~ /malloc\((\d*)\) = 0x([0-9a-f]*)/) {
    139             $size = $1;
    140             $addr = $2;
    141 
    142             if($sizeataddr{$addr}>0) {
    143                 # this means weeeeeirdo
    144                 print "Mixed debug compile ($source:$linenum at line $lnum), rebuild curl now\n";
    145                 print "We think $sizeataddr{$addr} bytes are already allocated at that memory address: $addr!\n";
    146             }
    147 
    148             $sizeataddr{$addr}=$size;
    149             $totalmem += $size;
    150 
    151             if($trace) {
    152                 print "MALLOC: malloc($size) at $source:$linenum",
    153                 " makes totally $totalmem bytes\n";
    154             }
    155 
    156             newtotal($totalmem);
    157             $mallocs++;
    158 
    159             $getmem{$addr}="$source:$linenum";
    160         }
    161         elsif($function =~ /calloc\((\d*),(\d*)\) = 0x([0-9a-f]*)/) {
    162             $size = $1*$2;
    163             $addr = $3;
    164 
    165             $arg1 = $1;
    166             $arg2 = $2;
    167 
    168             if($sizeataddr{$addr}>0) {
    169                 # this means weeeeeirdo
    170                 print "Mixed debug compile, rebuild curl now\n";
    171             }
    172 
    173             $sizeataddr{$addr}=$size;
    174             $totalmem += $size;
    175 
    176             if($trace) {
    177                 print "CALLOC: calloc($arg1,$arg2) at $source:$linenum",
    178                 " makes totally $totalmem bytes\n";
    179             }
    180 
    181             newtotal($totalmem);
    182             $callocs++;
    183 
    184             $getmem{$addr}="$source:$linenum";
    185         }
    186         elsif($function =~ /realloc\((\(nil\)|0x([0-9a-f]*)), (\d*)\) = 0x([0-9a-f]*)/) {
    187             my ($oldaddr, $newsize, $newaddr) = ($2, $3, $4);
    188 
    189             $totalmem -= $sizeataddr{$oldaddr};
    190             if($trace) {
    191                 printf("REALLOC: %d less bytes and ", $sizeataddr{$oldaddr});
    192             }
    193             $sizeataddr{$oldaddr}=0;
    194 
    195             $totalmem += $newsize;
    196             $sizeataddr{$newaddr}=$newsize;
    197 
    198             if($trace) {
    199                 printf("%d more bytes ($source:$linenum)\n", $newsize);
    200             }
    201 
    202             newtotal($totalmem);
    203             $reallocs++;
    204 
    205             $getmem{$oldaddr}="";
    206             $getmem{$newaddr}="$source:$linenum";
    207         }
    208         elsif($function =~ /strdup\(0x([0-9a-f]*)\) \((\d*)\) = 0x([0-9a-f]*)/) {
    209             # strdup(a5b50) (8) = df7c0
    210 
    211             $dup = $1;
    212             $size = $2;
    213             $addr = $3;
    214             $getmem{$addr}="$source:$linenum";
    215             $sizeataddr{$addr}=$size;
    216 
    217             $totalmem += $size;
    218 
    219             if($trace) {
    220                 printf("STRDUP: $size bytes at %s, makes totally: %d bytes\n",
    221                        $getmem{$addr}, $totalmem);
    222             }
    223 
    224             newtotal($totalmem);
    225             $strdups++;
    226         }
    227         elsif($function =~ /wcsdup\(0x([0-9a-f]*)\) \((\d*)\) = 0x([0-9a-f]*)/) {
    228             # wcsdup(a5b50) (8) = df7c0
    229 
    230             $dup = $1;
    231             $size = $2;
    232             $addr = $3;
    233             $getmem{$addr}="$source:$linenum";
    234             $sizeataddr{$addr}=$size;
    235 
    236             $totalmem += $size;
    237 
    238             if($trace) {
    239                 printf("WCSDUP: $size bytes at %s, makes totally: %d bytes\n",
    240                        $getmem{$addr}, $totalmem);
    241             }
    242 
    243             newtotal($totalmem);
    244             $wcsdups++;
    245         }
    246         else {
    247             print "Not recognized input line: $function\n";
    248         }
    249     }
    250     # FD url.c:1282 socket() = 5
    251     elsif($_ =~ /^FD ([^ ]*):(\d*) (.*)/) {
    252         # generic match for the filename+linenumber
    253         $source = $1;
    254         $linenum = $2;
    255         $function = $3;
    256 
    257         if($function =~ /socket\(\) = (\d*)/) {
    258             $filedes{$1}=1;
    259             $getfile{$1}="$source:$linenum";
    260             $openfile++;
    261         }
    262         elsif($function =~ /socketpair\(\) = (\d*) (\d*)/) {
    263             $filedes{$1}=1;
    264             $getfile{$1}="$source:$linenum";
    265             $openfile++;
    266             $filedes{$2}=1;
    267             $getfile{$2}="$source:$linenum";
    268             $openfile++;
    269         }
    270         elsif($function =~ /accept\(\) = (\d*)/) {
    271             $filedes{$1}=1;
    272             $getfile{$1}="$source:$linenum";
    273             $openfile++;
    274         }
    275         elsif($function =~ /sclose\((\d*)\)/) {
    276             if($filedes{$1} != 1) {
    277                 print "Close without open: $line\n";
    278             }
    279             else {
    280                 $filedes{$1}=0; # closed now
    281                 $openfile--;
    282             }
    283         }
    284     }
    285     # FILE url.c:1282 fopen("blabla") = 0x5ddd
    286     elsif($_ =~ /^FILE ([^ ]*):(\d*) (.*)/) {
    287         # generic match for the filename+linenumber
    288         $source = $1;
    289         $linenum = $2;
    290         $function = $3;
    291 
    292         if($function =~ /f[d]*open\(\"(.*)\",\"([^\"]*)\"\) = (\(nil\)|0x([0-9a-f]*))/) {
    293             if($3 eq "(nil)") {
    294                 ;
    295             }
    296             else {
    297                 $fopen{$4}=1;
    298                 $fopenfile{$4}="$source:$linenum";
    299                 $fopens++;
    300             }
    301         }
    302         # fclose(0x1026c8)
    303         elsif($function =~ /fclose\(0x([0-9a-f]*)\)/) {
    304             if(!$fopen{$1}) {
    305                 print "fclose() without fopen(): $line\n";
    306             }
    307             else {
    308                 $fopen{$1}=0;
    309                 $fopens--;
    310             }
    311         }
    312     }
    313     # GETNAME url.c:1901 getnameinfo()
    314     elsif($_ =~ /^GETNAME ([^ ]*):(\d*) (.*)/) {
    315         # not much to do
    316     }
    317 
    318     # ADDR url.c:1282 getaddrinfo() = 0x5ddd
    319     elsif($_ =~ /^ADDR ([^ ]*):(\d*) (.*)/) {
    320         # generic match for the filename+linenumber
    321         $source = $1;
    322         $linenum = $2;
    323         $function = $3;
    324 
    325         if($function =~ /getaddrinfo\(\) = (\(nil\)|0x([0-9a-f]*))/) {
    326             my $add = $2;
    327             if($add eq "(nil)") {
    328                 ;
    329             }
    330             else {
    331                 $addrinfo{$add}=1;
    332                 $addrinfofile{$add}="$source:$linenum";
    333                 $addrinfos++;
    334             }
    335             if($trace) {
    336                 printf("GETADDRINFO ($source:$linenum)\n");
    337             }
    338         }
    339         # fclose(0x1026c8)
    340         elsif($function =~ /freeaddrinfo\(0x([0-9a-f]*)\)/) {
    341             if(!$addrinfo{$1}) {
    342                 print "freeaddrinfo() without getaddrinfo(): $line\n";
    343             }
    344             else {
    345                 $addrinfo{$1}=0;
    346                 $addrinfos--;
    347             }
    348             if($trace) {
    349                 printf("FREEADDRINFO ($source:$linenum)\n");
    350             }
    351         }
    352 
    353     }
    354     else {
    355         print "Not recognized prefix line: $line\n";
    356     }
    357 }
    358 close(FILE);
    359 
    360 if($totalmem) {
    361     print "Leak detected: memory still allocated: $totalmem bytes\n";
    362 
    363     for(keys %sizeataddr) {
    364         $addr = $_;
    365         $size = $sizeataddr{$addr};
    366         if($size > 0) {
    367             print "At $addr, there's $size bytes.\n";
    368             print " allocated by ".$getmem{$addr}."\n";
    369         }
    370     }
    371 }
    372 
    373 if($openfile) {
    374     for(keys %filedes) {
    375         if($filedes{$_} == 1) {
    376             print "Open file descriptor created at ".$getfile{$_}."\n";
    377         }
    378     }
    379 }
    380 
    381 if($fopens) {
    382     print "Open FILE handles left at:\n";
    383     for(keys %fopen) {
    384         if($fopen{$_} == 1) {
    385             print "fopen() called at ".$fopenfile{$_}."\n";
    386         }
    387     }
    388 }
    389 
    390 if($addrinfos) {
    391     print "IPv6-style name resolve data left at:\n";
    392     for(keys %addrinfofile) {
    393         if($addrinfo{$_} == 1) {
    394             print "getaddrinfo() called at ".$addrinfofile{$_}."\n";
    395         }
    396     }
    397 }
    398 
    399 if($verbose) {
    400     print "Mallocs: $mallocs\n",
    401     "Reallocs: $reallocs\n",
    402     "Callocs: $callocs\n",
    403     "Strdups:  $strdups\n",
    404     "Wcsdups:  $wcsdups\n",
    405     "Frees: $frees\n",
    406     "Allocations: ".($mallocs + $callocs + $reallocs + $strdups + $wcsdups)."\n";
    407 
    408     print "Maximum allocated: $maxmem\n";
    409 }
    410