1#!/usr/bin/env perl 2#*************************************************************************** 3# _ _ ____ _ 4# Project ___| | | | _ \| | 5# / __| | | | |_) | | 6# | (__| |_| | _ <| |___ 7# \___|\___/|_| \_\_____| 8# 9# Copyright (C) 1998 - 2020, 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########################################################################### 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