xf.li | 6c8fc1e | 2023-08-12 00:11:09 -0700 | [diff] [blame^] | 1 | #!/usr/bin/env perl |
| 2 | #*************************************************************************** |
| 3 | # _ _ ____ _ |
| 4 | # Project ___| | | | _ \| | |
| 5 | # / __| | | | |_) | | |
| 6 | # | (__| |_| | _ <| |___ |
| 7 | # \___|\___/|_| \_\_____| |
| 8 | # |
| 9 | # Copyright (C) 1998 - 2022, 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 | |
| 32 | my $mallocs=0; |
| 33 | my $callocs=0; |
| 34 | my $reallocs=0; |
| 35 | my $strdups=0; |
| 36 | my $wcsdups=0; |
| 37 | my $showlimit; |
| 38 | my $sends=0; |
| 39 | my $recvs=0; |
| 40 | my $sockets=0; |
| 41 | |
| 42 | while(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 | |
| 61 | my $memsum; # the total number of memory allocated over the lifetime |
| 62 | my $maxmem; # the high water mark |
| 63 | |
| 64 | sub newtotal { |
| 65 | my ($newtot)=@_; |
| 66 | # count a max here |
| 67 | |
| 68 | if($newtot > $maxmem) { |
| 69 | $maxmem= $newtot; |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | my $file = $ARGV[0]; |
| 74 | |
| 75 | if(! -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 | |
| 84 | open(FILE, "<$file"); |
| 85 | |
| 86 | if($showlimit) { |
| 87 | while(<FILE>) { |
| 88 | if(/^LIMIT.*memlimit$/) { |
| 89 | print $_; |
| 90 | last; |
| 91 | } |
| 92 | } |
| 93 | close(FILE); |
| 94 | exit; |
| 95 | } |
| 96 | |
| 97 | |
| 98 | my $lnum=0; |
| 99 | while(<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 | } |
| 378 | close(FILE); |
| 379 | |
| 380 | if($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 | |
| 393 | if($openfile) { |
| 394 | for(keys %filedes) { |
| 395 | if($filedes{$_} == 1) { |
| 396 | print "Open file descriptor created at ".$getfile{$_}."\n"; |
| 397 | } |
| 398 | } |
| 399 | } |
| 400 | |
| 401 | if($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 | |
| 410 | if($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 | |
| 419 | if($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 | } |