2
0
mirror of https://github.com/openvswitch/ovs synced 2025-10-25 15:07:05 +00:00
Files
openvswitch/utilities/ovs-parse-leaks.in
2009-06-15 15:11:30 -07:00

300 lines
8.8 KiB
Plaintext
Executable File

#! @PERL@
# Copyright (c) 2009 Nicira Networks.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
use strict;
use warnings;
if (grep($_ eq '--help', @ARGV)) {
print <<EOF;
$0, for parsing leak checker logs
usage: $0 [BINARY] < LOG
where LOG is a file produced by an Open vSwitch program's --check-leaks option
and BINARY is the binary that wrote LOG.
EOF
exit 0;
}
die "$0: zero or one arguments required; use --help for help\n" if @ARGV > 1;
die "$0: $ARGV[0] does not exist" if @ARGV > 0 && ! -e $ARGV[0];
our ($binary);
our ($a2l) = search_path("addr2line");
my ($no_syms) = "symbols will not be translated";
if (!@ARGV) {
print "no binary specified; $no_syms\n";
} elsif (! -e $ARGV[0]) {
print "$ARGV[0] does not exist; $no_syms";
} elsif (!defined($a2l)) {
print "addr2line not found in PATH; $no_syms";
} else {
$binary = $ARGV[0];
}
our ($objdump) = search_path("objdump");
print "objdump not found; dynamic library symbols will not be translated\n"
if !defined($objdump);
our %blocks;
our @segments;
while (<STDIN>) {
my $ptr = "((?:0x)?[0-9a-fA-F]+|\\(nil\\))";
my $callers = ":((?: $ptr)+)";
if (/^malloc\((\d+)\) -> $ptr$callers$/) {
allocated($., $2, $1, $3);
} elsif (/^claim\($ptr\)$callers$/) {
claimed($., $1, $2);
} elsif (/realloc\($ptr, (\d+)\) -> $ptr$callers$/) {
my ($callers) = $4;
freed($., $1, $callers);
allocated($., $3, $2, $callers);
} elsif (/^free\($ptr\)$callers$/) {
freed($., $1, $2);
} elsif (/^segment: $ptr-$ptr $ptr [-r][-w][-x][sp] (.*)/) {
add_segment(hex($1), hex($2), hex($3), $4);
} else {
print "stdin:$.: syntax error\n";
}
}
if (%blocks) {
my $n_blocks = scalar(keys(%blocks));
my $n_bytes = 0;
$n_bytes += $_->{SIZE} foreach values(%blocks);
print "$n_bytes bytes in $n_blocks blocks not freed at end of run\n";
my %blocks_by_callers;
foreach my $block (values(%blocks)) {
my ($trimmed_callers) = trim_callers($block->{CALLERS});
push (@{$blocks_by_callers{$trimmed_callers}}, $block);
}
foreach my $callers (sort {@{$b} <=> @{$a}} (values(%blocks_by_callers))) {
$n_blocks = scalar(@{$callers});
$n_bytes = 0;
$n_bytes += $_->{SIZE} foreach @{$callers};
print "$n_bytes bytes in these $n_blocks blocks were not freed:\n";
my $i = 0;
my $max = 5;
foreach my $block (sort {$a->{LINE} <=> $b->{LINE}} (@{$callers})) {
printf "\t%d-byte block at 0x%08x allocated on stdin:%d\n",
$block->{SIZE}, $block->{BASE}, $block->{LINE};
last if $i++ > $max;
}
print "\t...and ", $n_blocks - $max, " others...\n"
if $n_blocks > $max;
print "The blocks listed above were allocated by:\n";
print_callers("\t", ${$callers}[0]->{CALLERS});
}
}
sub interp_pointer {
my ($s_ptr) = @_;
return $s_ptr eq '(nil)' ? 0 : hex($s_ptr);
}
sub allocated {
my ($line, $s_base, $size, $callers) = @_;
my ($base) = interp_pointer($s_base);
return if !$base;
my ($info) = {LINE => $line,
BASE => $base,
SIZE => $size,
CALLERS => $callers};
if (exists($blocks{$base})) {
print "In-use address returned by allocator:\n";
print "\tInitial allocation:\n";
print_block("\t\t", $blocks{$base});
print "\tNew allocation:\n";
print_block("\t\t", $info);
}
$blocks{$base} = $info;
}
sub claimed {
my ($line, $s_base, $callers) = @_;
my ($base) = interp_pointer($s_base);
return if !$base;
if (exists($blocks{$base})) {
$blocks{$base}{LINE} = $line;
$blocks{$base}{CALLERS} = $callers;
} else {
printf "Claim asserted on not-in-use block 0x%08x by:\n", $base;
print_callers('', $callers);
}
}
sub freed {
my ($line, $s_base, $callers) = @_;
my ($base) = interp_pointer($s_base);
return if !$base;
if (!delete($blocks{$base})) {
printf "Bad free of not-allocated address 0x%08x on stdin:%d by:\n", $base, $line;
print_callers('', $callers);
}
}
sub print_block {
my ($prefix, $info) = @_;
printf '%s%d-byte block at 0x%08x allocated on stdin:%d by:' . "\n",
$prefix, $info->{SIZE}, $info->{BASE}, $info->{LINE};
print_callers($prefix, $info->{CALLERS});
}
sub print_callers {
my ($prefix, $callers) = @_;
foreach my $pc (split(' ', $callers)) {
print "$prefix\t", lookup_pc($pc), "\n";
}
}
our (%cache);
sub lookup_pc {
my ($s_pc) = @_;
if (defined($binary)) {
my ($pc) = hex($s_pc);
my ($output) = "$s_pc: ";
if (!exists($cache{$pc})) {
open(A2L, "$a2l -fe $binary --demangle $s_pc|");
chomp(my $function = <A2L>);
chomp(my $line = <A2L>);
close(A2L);
if ($function eq '??') {
($function, $line) = lookup_pc_by_segment($pc);
}
$line =~ s/^(\.\.\/)*//;
$line = "..." . substr($line, -25) if length($line) > 28;
$cache{$pc} = "$s_pc: $function ($line)";
}
return $cache{$pc};
} else {
return "$s_pc";
}
}
sub trim_callers {
my ($in) = @_;
my (@out);
foreach my $pc (split(' ', $in)) {
my $xlated = lookup_pc($pc);
if ($xlated =~ /\?\?/) {
push(@out, "...") if !@out || $out[$#out] ne '...';
} else {
push(@out, $pc);
}
}
return join(' ', @out);
}
sub search_path {
my ($target) = @_;
for my $dir (split (':', $ENV{PATH})) {
my ($file) = "$dir/$target";
return $file if -e $file;
}
return undef;
}
sub add_segment {
my ($vm_start, $vm_end, $vm_pgoff, $file) = @_;
for (my $i = 0; $i <= $#segments; $i++) {
my ($s) = $segments[$i];
next if $vm_end <= $s->{START} || $vm_start >= $s->{END};
if ($vm_start <= $s->{START} && $vm_end >= $s->{END}) {
splice(@segments, $i, 1);
--$i;
} else {
$s->{START} = $vm_end if $vm_end > $s->{START};
$s->{END} = $vm_start if $vm_start <= $s->{END};
}
}
push(@segments, {START => $vm_start,
END => $vm_end,
PGOFF => $vm_pgoff,
FILE => $file});
@segments = sort { $a->{START} <=> $b->{START} } @segments;
}
sub binary_search {
my ($array, $value) = @_;
my $l = 0;
my $r = $#{$array};
while ($l <= $r) {
my $m = int(($l + $r) / 2);
my $e = $array->[$m];
if ($value < $e->{START}) {
$r = $m - 1;
} elsif ($value >= $e->{END}) {
$l = $m + 1;
} else {
return $e;
}
}
return undef;
}
sub read_sections {
my ($file) = @_;
my (@sections);
open(OBJDUMP, "$objdump -h $file|");
while (<OBJDUMP>) {
my $ptr = "([0-9a-fA-F]+)";
my ($name, $size, $vma, $lma, $file_off)
= /^\s*\d+\s+(\S+)\s+$ptr\s+$ptr\s+$ptr\s+$ptr/
or next;
push(@sections, {START => hex($file_off),
END => hex($file_off) + hex($size),
NAME => $name});
}
close(OBJDUMP);
return [sort { $a->{START} <=> $b->{START} } @sections ];
}
our %file_to_sections;
sub segment_to_section {
my ($file, $file_offset) = @_;
if (!defined($file_to_sections{$file})) {
$file_to_sections{$file} = read_sections($file);
}
return binary_search($file_to_sections{$file}, $file_offset);
}
sub address_to_segment {
my ($pc) = @_;
return binary_search(\@segments, $pc);
}
sub lookup_pc_by_segment {
return ('??', 0) if !defined($objdump);
my ($pc) = @_;
my ($segment) = address_to_segment($pc);
return ('??', 0) if !defined($segment) || $segment->{FILE} eq '';
my ($file_offset) = $pc - $segment->{START} + $segment->{PGOFF};
my ($section) = segment_to_section($segment->{FILE}, $file_offset);
return ('??', 0) if !defined($section);
my ($section_offset) = $file_offset - $section->{START};
open(A2L, sprintf("%s -fe %s --demangle --section=$section->{NAME} 0x%x|",
$a2l, $segment->{FILE}, $section_offset));
chomp(my $function = <A2L>);
chomp(my $line = <A2L>);
close(A2L);
return ($function, $line);
}
# Local Variables:
# mode: perl
# End: