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