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