Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | #!/usr/bin/perl -w |
michael@0 | 2 | # |
michael@0 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 6 | |
michael@0 | 7 | use 5.004; |
michael@0 | 8 | use strict; |
michael@0 | 9 | use Getopt::Long; |
michael@0 | 10 | |
michael@0 | 11 | $::opt_prune_depth = 0; |
michael@0 | 12 | $::opt_subtree_size = 0; |
michael@0 | 13 | $::opt_reverse = 0; |
michael@0 | 14 | |
michael@0 | 15 | # GetOption will create $opt_object & $opt_exclude, so ignore the |
michael@0 | 16 | # warning that gets spit out about those vbls. |
michael@0 | 17 | GetOptions("object=s", "exclude=s", "comptrs=s", "ignore-balanced", "subtree-size=i", "prune-depth=i", |
michael@0 | 18 | "collapse-to-method", "collapse-to-class", "old-style", "reverse"); |
michael@0 | 19 | |
michael@0 | 20 | $::opt_object || |
michael@0 | 21 | die qq{ |
michael@0 | 22 | usage: leak.pl < logfile |
michael@0 | 23 | --object <obj> The address of the object to examine (required) |
michael@0 | 24 | --exclude <file> Exclude routines listed in <file> |
michael@0 | 25 | --comptrs <file> Subtract all the data in the balanced COMPtr log <file> |
michael@0 | 26 | --ignore-balanced Ignore balanced subtrees |
michael@0 | 27 | --subtree-size <n> Print subtrees with more than <n> nodes separately |
michael@0 | 28 | --prune-depth <depth> Prune the tree to <depth> |
michael@0 | 29 | --collapse-to-method Aggregate data by method |
michael@0 | 30 | --collapse-to-class Aggregate data by class (subsumes --collapse-to-method) |
michael@0 | 31 | --reverse Reverse call stacks, showing leaves first |
michael@0 | 32 | --old-style Old-style formatting |
michael@0 | 33 | }; |
michael@0 | 34 | |
michael@0 | 35 | $::opt_prune_depth = 0 if $::opt_prune_depth < 0; |
michael@0 | 36 | $::opt_subtree_size = 0 if $::opt_subtree_size < 0; |
michael@0 | 37 | |
michael@0 | 38 | warn "object $::opt_object\n"; |
michael@0 | 39 | warn "ignoring balanced subtrees\n" if $::opt_ignore_balanced; |
michael@0 | 40 | warn "prune depth $::opt_prune_depth\n" if $::opt_prune_depth; |
michael@0 | 41 | warn "collapsing to class\n" if $::opt_collapse_to_class; |
michael@0 | 42 | warn "collapsing to method\n" if $::opt_collapse_to_method && !$::opt_collapse_to_class; |
michael@0 | 43 | warn "reversing call stacks\n" if $::opt_reverse; |
michael@0 | 44 | |
michael@0 | 45 | |
michael@0 | 46 | # The 'excludes' are functions that, if detected in a particular call |
michael@0 | 47 | # stack, will cause the _entire_ call stack to be ignored. You might, |
michael@0 | 48 | # for example, explicitly exclude two functions that have a matching |
michael@0 | 49 | # AddRef/Release pair. |
michael@0 | 50 | |
michael@0 | 51 | my %excludes; |
michael@0 | 52 | |
michael@0 | 53 | if ($::opt_exclude) { |
michael@0 | 54 | open(EXCLUDE, "<".$::opt_exclude) |
michael@0 | 55 | || die "unable to open $::opt_exclude"; |
michael@0 | 56 | |
michael@0 | 57 | while (<EXCLUDE>) { |
michael@0 | 58 | chomp $_; |
michael@0 | 59 | warn "excluding $_\n"; |
michael@0 | 60 | $excludes{$_} = 1; |
michael@0 | 61 | } |
michael@0 | 62 | } |
michael@0 | 63 | |
michael@0 | 64 | # Each entry in the tree rooted by callGraphRoot contains the following: |
michael@0 | 65 | # #name# This call's name+offset string |
michael@0 | 66 | # #refcount# The net reference count of this call |
michael@0 | 67 | # #label# The label used for this subtree; only defined for labeled nodes |
michael@0 | 68 | # #children# List of children in alphabetical order |
michael@0 | 69 | # zero or more children indexed by method name+offset strings. |
michael@0 | 70 | |
michael@0 | 71 | my $callGraphRoot; |
michael@0 | 72 | $callGraphRoot = { '#name#' => '.root', '#refcount#' => 'n/a' }; |
michael@0 | 73 | |
michael@0 | 74 | # The 'imbalance' is a gross count of how balanced a particular |
michael@0 | 75 | # callsite is. It is used to prune away callsites that are detected to |
michael@0 | 76 | # be balanced; that is, that have matching AddRef/Release() pairs. |
michael@0 | 77 | |
michael@0 | 78 | my %imbalance; |
michael@0 | 79 | $imbalance{'.root'} = 'n/a'; |
michael@0 | 80 | |
michael@0 | 81 | # The main read loop. |
michael@0 | 82 | |
michael@0 | 83 | sub read_data($$$) { |
michael@0 | 84 | my ($INFILE, $plus, $minus) = @_; |
michael@0 | 85 | |
michael@0 | 86 | LINE: while (<$INFILE>) { |
michael@0 | 87 | next LINE if (! /^</); |
michael@0 | 88 | my @fields = split(/ /, $_); |
michael@0 | 89 | |
michael@0 | 90 | my $class = shift(@fields); |
michael@0 | 91 | my $obj = shift(@fields); |
michael@0 | 92 | my $sno = shift(@fields); |
michael@0 | 93 | next LINE unless ($obj eq $::opt_object); |
michael@0 | 94 | |
michael@0 | 95 | my $op = shift(@fields); |
michael@0 | 96 | next LINE unless ($op eq $plus || $op eq $minus); |
michael@0 | 97 | |
michael@0 | 98 | my $cnt = shift(@fields); |
michael@0 | 99 | |
michael@0 | 100 | # Collect the remaining lines to create a stack trace. |
michael@0 | 101 | my @stack; |
michael@0 | 102 | CALLSITE: while (<$INFILE>) { |
michael@0 | 103 | chomp; |
michael@0 | 104 | last CALLSITE if (/^$/); |
michael@0 | 105 | $stack[++$#stack] = $_; |
michael@0 | 106 | } |
michael@0 | 107 | |
michael@0 | 108 | # Reverse the remaining fields to produce the call stack, with the |
michael@0 | 109 | # oldest frame at the front of the array. |
michael@0 | 110 | if (! $::opt_reverse) { |
michael@0 | 111 | @stack = reverse(@stack); |
michael@0 | 112 | } |
michael@0 | 113 | |
michael@0 | 114 | my $call; |
michael@0 | 115 | |
michael@0 | 116 | # If any of the functions in the stack are supposed to be excluded, |
michael@0 | 117 | # march on to the next line. |
michael@0 | 118 | foreach $call (@stack) { |
michael@0 | 119 | next LINE if exists($excludes{$call}); |
michael@0 | 120 | } |
michael@0 | 121 | |
michael@0 | 122 | |
michael@0 | 123 | # Add the callstack as a path through the call graph, updating |
michael@0 | 124 | # refcounts at each node. |
michael@0 | 125 | |
michael@0 | 126 | my $caller = $callGraphRoot; |
michael@0 | 127 | |
michael@0 | 128 | foreach $call (@stack) { |
michael@0 | 129 | |
michael@0 | 130 | # Chop the method offset if we're 'collapsing to method' or |
michael@0 | 131 | # 'collapsing to class'. |
michael@0 | 132 | $call =~ s/\+0x.*$//g if ($::opt_collapse_to_method || $::opt_collapse_to_class); |
michael@0 | 133 | |
michael@0 | 134 | # Chop the method name if we're 'collapsing to class'. |
michael@0 | 135 | $call =~ s/::.*$//g if ($::opt_collapse_to_class); |
michael@0 | 136 | |
michael@0 | 137 | my $site = $caller->{$call}; |
michael@0 | 138 | if (!$site) { |
michael@0 | 139 | # This is the first time we've seen this callsite. Add a |
michael@0 | 140 | # new entry to the call tree. |
michael@0 | 141 | |
michael@0 | 142 | $site = { '#name#' => $call, '#refcount#' => 0 }; |
michael@0 | 143 | $caller->{$call} = $site; |
michael@0 | 144 | } |
michael@0 | 145 | |
michael@0 | 146 | if ($op eq $plus) { |
michael@0 | 147 | ++($site->{'#refcount#'}); |
michael@0 | 148 | ++($imbalance{$call}); |
michael@0 | 149 | } elsif ($op eq $minus) { |
michael@0 | 150 | --($site->{'#refcount#'}); |
michael@0 | 151 | --($imbalance{$call}); |
michael@0 | 152 | } else { |
michael@0 | 153 | die "Bad operation $op"; |
michael@0 | 154 | } |
michael@0 | 155 | |
michael@0 | 156 | $caller = $site; |
michael@0 | 157 | } |
michael@0 | 158 | } |
michael@0 | 159 | } |
michael@0 | 160 | |
michael@0 | 161 | read_data(*STDIN, "AddRef", "Release"); |
michael@0 | 162 | |
michael@0 | 163 | if ($::opt_comptrs) { |
michael@0 | 164 | warn "Subtracting comptr log ". $::opt_comptrs . "\n"; |
michael@0 | 165 | open(COMPTRS, "<".$::opt_comptrs) |
michael@0 | 166 | || die "unable to open $::opt_comptrs"; |
michael@0 | 167 | |
michael@0 | 168 | # read backwards to subtract |
michael@0 | 169 | read_data(*COMPTRS, "nsCOMPtrRelease", "nsCOMPtrAddRef"); |
michael@0 | 170 | } |
michael@0 | 171 | |
michael@0 | 172 | sub num_alpha { |
michael@0 | 173 | my ($aN, $aS, $bN, $bS); |
michael@0 | 174 | ($aN, $aS) = ($1, $2) if $a =~ /^(\d+) (.+)$/; |
michael@0 | 175 | ($bN, $bS) = ($1, $2) if $b =~ /^(\d+) (.+)$/; |
michael@0 | 176 | return $a cmp $b unless defined $aN && defined $bN; |
michael@0 | 177 | return $aN <=> $bN unless $aN == $bN; |
michael@0 | 178 | return $aS cmp $bS; |
michael@0 | 179 | } |
michael@0 | 180 | |
michael@0 | 181 | # Given a subtree and its nesting level, return true if that subtree should be pruned. |
michael@0 | 182 | # If it shouldn't be pruned, destructively attempt to prune its children. |
michael@0 | 183 | # Also compute the #children# properties of unpruned nodes. |
michael@0 | 184 | sub prune($$) { |
michael@0 | 185 | my ($site, $nest) = @_; |
michael@0 | 186 | |
michael@0 | 187 | # If they want us to prune the tree's depth, do so here. |
michael@0 | 188 | return 1 if ($::opt_prune_depth && $nest >= $::opt_prune_depth); |
michael@0 | 189 | |
michael@0 | 190 | # If the subtree is balanced, ignore it. |
michael@0 | 191 | return 1 if ($::opt_ignore_balanced && !$site->{'#refcount#'}); |
michael@0 | 192 | |
michael@0 | 193 | my $name = $site->{'#name#'}; |
michael@0 | 194 | |
michael@0 | 195 | # If the symbol isn't imbalanced, then prune here (and warn) |
michael@0 | 196 | if ($::opt_ignore_balanced && !$imbalance{$name}) { |
michael@0 | 197 | warn "discarding " . $name . "\n"; |
michael@0 | 198 | # return 1; |
michael@0 | 199 | } |
michael@0 | 200 | |
michael@0 | 201 | my @children; |
michael@0 | 202 | foreach my $child (sort num_alpha keys(%$site)) { |
michael@0 | 203 | if (substr($child, 0, 1) ne '#') { |
michael@0 | 204 | if (prune($site->{$child}, $nest + 1)) { |
michael@0 | 205 | delete $site->{$child}; |
michael@0 | 206 | } else { |
michael@0 | 207 | push @children, $site->{$child}; |
michael@0 | 208 | } |
michael@0 | 209 | } |
michael@0 | 210 | } |
michael@0 | 211 | $site->{'#children#'} = \@children; |
michael@0 | 212 | return 0; |
michael@0 | 213 | } |
michael@0 | 214 | |
michael@0 | 215 | |
michael@0 | 216 | # Compute the #label# properties of this subtree. |
michael@0 | 217 | # Return the subtree's number of nodes, not counting nodes reachable |
michael@0 | 218 | # through a labeled node. |
michael@0 | 219 | sub createLabels($) { |
michael@0 | 220 | my ($site) = @_; |
michael@0 | 221 | my @children = @{$site->{'#children#'}}; |
michael@0 | 222 | my $nChildren = @children; |
michael@0 | 223 | my $nDescendants = 0; |
michael@0 | 224 | |
michael@0 | 225 | foreach my $child (@children) { |
michael@0 | 226 | my $childDescendants = createLabels($child); |
michael@0 | 227 | if ($nChildren > 1 && $childDescendants > $::opt_subtree_size) { |
michael@0 | 228 | die "Internal error" if defined($child->{'#label#'}); |
michael@0 | 229 | $child->{'#label#'} = "__label__"; |
michael@0 | 230 | $childDescendants = 1; |
michael@0 | 231 | } |
michael@0 | 232 | $nDescendants += $childDescendants; |
michael@0 | 233 | } |
michael@0 | 234 | return $nDescendants + 1; |
michael@0 | 235 | } |
michael@0 | 236 | |
michael@0 | 237 | |
michael@0 | 238 | my $nextLabel = 0; |
michael@0 | 239 | my @labeledSubtrees; |
michael@0 | 240 | |
michael@0 | 241 | sub list($$$$$) { |
michael@0 | 242 | my ($site, $nest, $nestStr, $childrenLeft, $root) = @_; |
michael@0 | 243 | my $label = !$root && $site->{'#label#'}; |
michael@0 | 244 | |
michael@0 | 245 | # Assign a unique number to the label. |
michael@0 | 246 | if ($label) { |
michael@0 | 247 | die unless $label eq "__label__"; |
michael@0 | 248 | $label = "__" . ++$nextLabel . "__"; |
michael@0 | 249 | $site->{'#label#'} = $label; |
michael@0 | 250 | push @labeledSubtrees, $site; |
michael@0 | 251 | } |
michael@0 | 252 | |
michael@0 | 253 | print $nestStr; |
michael@0 | 254 | if ($::opt_old_style) { |
michael@0 | 255 | print $label, " " if $label; |
michael@0 | 256 | print $site->{'#name#'}, ": bal=", $site->{'#refcount#'}, "\n"; |
michael@0 | 257 | } else { |
michael@0 | 258 | my $refcount = $site->{'#refcount#'}; |
michael@0 | 259 | my $l = 8 - length $refcount; |
michael@0 | 260 | $l = 1 if $l < 1; |
michael@0 | 261 | print $refcount, " " x $l; |
michael@0 | 262 | print $label, " " if $label; |
michael@0 | 263 | print $site->{'#name#'}, "\n"; |
michael@0 | 264 | } |
michael@0 | 265 | |
michael@0 | 266 | $nestStr .= $childrenLeft && !$::opt_old_style ? "| " : " "; |
michael@0 | 267 | if (!$label) { |
michael@0 | 268 | my @children = @{$site->{'#children#'}}; |
michael@0 | 269 | $childrenLeft = @children; |
michael@0 | 270 | foreach my $child (@children) { |
michael@0 | 271 | $childrenLeft--; |
michael@0 | 272 | list($child, $nest + 1, $nestStr, $childrenLeft); |
michael@0 | 273 | } |
michael@0 | 274 | } |
michael@0 | 275 | } |
michael@0 | 276 | |
michael@0 | 277 | |
michael@0 | 278 | if (!prune($callGraphRoot, 0)) { |
michael@0 | 279 | createLabels $callGraphRoot if ($::opt_subtree_size); |
michael@0 | 280 | list $callGraphRoot, 0, "", 0, 1; |
michael@0 | 281 | while (@labeledSubtrees) { |
michael@0 | 282 | my $labeledSubtree = shift @labeledSubtrees; |
michael@0 | 283 | print "\n------------------------------\n", |
michael@0 | 284 | $labeledSubtree->{'#label#'}, "\n"; |
michael@0 | 285 | list $labeledSubtree, 0, "", 0, 1; |
michael@0 | 286 | } |
michael@0 | 287 | print "\n------------------------------\n" if @labeledSubtrees; |
michael@0 | 288 | } |
michael@0 | 289 | |
michael@0 | 290 | print qq{ |
michael@0 | 291 | Imbalance |
michael@0 | 292 | --------- |
michael@0 | 293 | }; |
michael@0 | 294 | |
michael@0 | 295 | foreach my $call (sort num_alpha keys(%imbalance)) { |
michael@0 | 296 | print $call . " " . $imbalance{$call} . "\n"; |
michael@0 | 297 | } |
michael@0 | 298 |