1#!/usr/bin/env perl
2#***************************************************************************
3#                                  _   _ ____  _
4#  Project                     ___| | | |  _ \| |
5#                             / __| | | | |_) | |
6#                            | (__| |_| |  _ <| |___
7#                             \___|\___/|_| \_\_____|
8#
9# Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@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
30my $mallocs=0;
31my $callocs=0;
32my $reallocs=0;
33my $strdups=0;
34my $wcsdups=0;
35my $showlimit;
36my $sends=0;
37my $recvs=0;
38my $sockets=0;
39
40while(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
59my $maxmem;
60
61sub newtotal {
62    my ($newtot)=@_;
63    # count a max here
64
65    if($newtot > $maxmem) {
66        $maxmem= $newtot;
67    }
68}
69
70my $file = $ARGV[0];
71
72if(! -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
81open(FILE, "<$file");
82
83if($showlimit) {
84    while(<FILE>) {
85        if(/^LIMIT.*memlimit$/) {
86            print $_;
87            last;
88        }
89    }
90    close(FILE);
91    exit;
92}
93
94
95my $lnum=0;
96while(<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}
370close(FILE);
371
372if($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
385if($openfile) {
386    for(keys %filedes) {
387        if($filedes{$_} == 1) {
388            print "Open file descriptor created at ".$getfile{$_}."\n";
389        }
390    }
391}
392
393if($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
402if($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
411if($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