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(FILE, "<$file"); 85 86if($showlimit) { 87 while(<FILE>) { 88 if(/^LIMIT.*memlimit$/) { 89 print $_; 90 last; 91 } 92 } 93 close(FILE); 94 exit; 95} 96 97 98my $lnum=0; 99while(<FILE>) { 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(FILE); 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