run-leaks 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. #!/usr/bin/perl
  2. # Copyright (C) 2007 Apple Inc. All rights reserved.
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions
  6. # are met:
  7. #
  8. # 1. Redistributions of source code must retain the above copyright
  9. # notice, this list of conditions and the following disclaimer.
  10. # 2. Redistributions in binary form must reproduce the above copyright
  11. # notice, this list of conditions and the following disclaimer in the
  12. # documentation and/or other materials provided with the distribution.
  13. # 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
  14. # its contributors may be used to endorse or promote products derived
  15. # from this software without specific prior written permission.
  16. #
  17. # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
  18. # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  19. # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  20. # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
  21. # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  22. # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  23. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  24. # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  26. # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. # Script to run the Mac OS X leaks tool with more expressive '-exclude' lists.
  28. use strict;
  29. use warnings;
  30. use File::Basename;
  31. use Getopt::Long;
  32. sub runLeaks($);
  33. sub parseLeaksOutput(\@);
  34. sub removeMatchingRecords(\@$\@);
  35. sub reportError($);
  36. sub main()
  37. {
  38. # Read options.
  39. my $usage =
  40. "Usage: " . basename($0) . " [options] pid | executable name\n" .
  41. " --exclude-callstack regexp Exclude leaks whose call stacks match the regular expression 'regexp'.\n" .
  42. " --exclude-type regexp Exclude leaks whose data types match the regular expression 'regexp'.\n" .
  43. " --help Show this help message.\n";
  44. my @callStacksToExclude = ();
  45. my @typesToExclude = ();
  46. my $help = 0;
  47. my $getOptionsResult = GetOptions(
  48. 'exclude-callstack:s' => \@callStacksToExclude,
  49. 'exclude-type:s' => \@typesToExclude,
  50. 'help' => \$help
  51. );
  52. my $pidOrExecutableName = $ARGV[0];
  53. if (!$getOptionsResult || $help) {
  54. print STDERR $usage;
  55. return 1;
  56. }
  57. if (!$pidOrExecutableName) {
  58. reportError("Missing argument: pid | executable.");
  59. print STDERR $usage;
  60. return 1;
  61. }
  62. # Run leaks tool.
  63. my $leaksOutput = runLeaks($pidOrExecutableName);
  64. if (!$leaksOutput) {
  65. return 1;
  66. }
  67. my $leakList = parseLeaksOutput(@$leaksOutput);
  68. if (!$leakList) {
  69. return 1;
  70. }
  71. # Filter output.
  72. my $leakCount = @$leakList;
  73. removeMatchingRecords(@$leakList, "callStack", @callStacksToExclude);
  74. removeMatchingRecords(@$leakList, "type", @typesToExclude);
  75. my $excludeCount = $leakCount - @$leakList;
  76. # Dump results.
  77. print $leaksOutput->[0];
  78. print $leaksOutput->[1];
  79. foreach my $leak (@$leakList) {
  80. print $leak->{"leaksOutput"};
  81. }
  82. if ($excludeCount) {
  83. print "$excludeCount leaks excluded (not printed)\n";
  84. }
  85. return 0;
  86. }
  87. exit(main());
  88. # Returns the output of the leaks tool in list form.
  89. sub runLeaks($)
  90. {
  91. my ($pidOrExecutableName) = @_;
  92. my @leaksOutput = `leaks $pidOrExecutableName`;
  93. if (!@leaksOutput) {
  94. reportError("Error running leaks tool.");
  95. return;
  96. }
  97. return \@leaksOutput;
  98. }
  99. # Returns a list of hash references with the keys { address, size, type, callStack, leaksOutput }
  100. sub parseLeaksOutput(\@)
  101. {
  102. my ($leaksOutput) = @_;
  103. # Format:
  104. # Process 00000: 1234 nodes malloced for 1234 KB
  105. # Process 00000: XX leaks for XXX total leaked bytes.
  106. # Leak: 0x00000000 size=1234 [instance of 'blah']
  107. # 0x00000000 0x00000000 0x00000000 0x00000000 a..d.e.e
  108. # ...
  109. # Call stack: leak_caller() | leak() | malloc
  110. #
  111. # We treat every line except for Process 00000: and Leak: as optional
  112. # Skip header section until the first two "Process " lines.
  113. # FIXME: In the future we may wish to propagate the header section through to our output.
  114. until ($leaksOutput->[0] =~ /^Process /) {
  115. shift @$leaksOutput;
  116. }
  117. my ($leakCount) = ($leaksOutput->[1] =~ /[[:blank:]]+([0-9]+)[[:blank:]]+leaks?/);
  118. if (!defined($leakCount)) {
  119. reportError("Could not parse leak count reported by leaks tool.");
  120. return;
  121. }
  122. my @leakList = ();
  123. for my $line (@$leaksOutput) {
  124. next if $line =~ /^Process/;
  125. next if $line =~ /^node buffer added/;
  126. if ($line =~ /^Leak: /) {
  127. my ($address) = ($line =~ /Leak: ([[:xdigit:]x]+)/);
  128. if (!defined($address)) {
  129. reportError("Could not parse Leak address.");
  130. return;
  131. }
  132. my ($size) = ($line =~ /size=([[:digit:]]+)/);
  133. if (!defined($size)) {
  134. reportError("Could not parse Leak size.");
  135. return;
  136. }
  137. my ($type) = ($line =~ /'([^']+)'/); #'
  138. if (!defined($type)) {
  139. $type = ""; # The leaks tool sometimes omits the type.
  140. }
  141. my %leak = (
  142. "address" => $address,
  143. "size" => $size,
  144. "type" => $type,
  145. "callStack" => "", # The leaks tool sometimes omits the call stack.
  146. "leaksOutput" => $line
  147. );
  148. push(@leakList, \%leak);
  149. } else {
  150. $leakList[$#leakList]->{"leaksOutput"} .= $line;
  151. if ($line =~ /Call stack:/) {
  152. $leakList[$#leakList]->{"callStack"} = $line;
  153. }
  154. }
  155. }
  156. if (@leakList != $leakCount) {
  157. my $parsedLeakCount = @leakList;
  158. reportError("Parsed leak count($parsedLeakCount) does not match leak count reported by leaks tool($leakCount).");
  159. return;
  160. }
  161. return \@leakList;
  162. }
  163. sub removeMatchingRecords(\@$\@)
  164. {
  165. my ($recordList, $key, $regexpList) = @_;
  166. RECORD: for (my $i = 0; $i < @$recordList;) {
  167. my $record = $recordList->[$i];
  168. foreach my $regexp (@$regexpList) {
  169. if ($record->{$key} =~ $regexp) {
  170. splice(@$recordList, $i, 1);
  171. next RECORD;
  172. }
  173. }
  174. $i++;
  175. }
  176. }
  177. sub reportError($)
  178. {
  179. my ($errorMessage) = @_;
  180. print STDERR basename($0) . ": $errorMessage\n";
  181. }