| #!/usr/bin/perl |
| |
| sub error { |
| print STDERR ("Error: @_\n"); |
| exit(1); |
| } |
| |
| sub warning { |
| print STDERR ("Warning: @_\n"); |
| } |
| |
| # string (filename.map) |
| # return hash(string:hash(string:number)) |
| sub read_map { |
| open(MAP_FILE,$_[0]) || error("Couldn't open a map $_[0]"); |
| my %retval; |
| while (<MAP_FILE>) { |
| chomp; |
| my @parts = split(/[[:space:]]+/); |
| if (@parts != 5) { |
| next; |
| } |
| if ($parts[1] =~ m/\.(text|data|rodata|bss|icode|idata|irodata|ibss)/) { |
| my $region = $parts[1]; |
| my $number = $parts[2]; |
| @parts = split(/\//,$parts[4]); |
| @parts = split(/[\(\)]/,$parts[$#parts]); |
| my $library = $retval{$parts[0]}; |
| my %library = %$library; |
| my $object = $parts[$#parts]; |
| $library{$object . $region} = $number; |
| $retval{$parts[0]} = \%library; |
| } |
| } |
| close(MAP_FILE); |
| return %retval; |
| } |
| |
| # string (filename.[ao]), hash(string:number) |
| # return hash(number:string) |
| sub read_library { |
| open(OBJECT_FILE,"objdump -t $_[0] |") || |
| error("Couldn't pipe objdump for $_[0]"); |
| my $library = $_[1]; |
| my %library = %$library; |
| my %retval; |
| my $object; |
| while (<OBJECT_FILE>) { |
| chomp; |
| my @parts = split(/[[:space:]]+/); |
| if ($parts[0] =~ m/:$/) { |
| $object = $parts[0]; |
| $object =~ s/:$//; |
| next; |
| } |
| if (@parts != 6) { |
| next; |
| } |
| if ($parts[0] eq "") { |
| next; |
| } |
| if ($parts[3] eq $parts[5]) { |
| next; |
| } |
| if ($parts[3] =~ m/\.(text|data|rodata|bss|icode|idata|irodata|ibss)/) { |
| my $region = $parts[3]; |
| my $symbolOffset = hex("0x" . $parts[0]); |
| my $sectionOffset = hex($library{$object . $region}); |
| my $location = $symbolOffset + $sectionOffset; |
| $retval{$location} = $parts[5] . "(" . $object . ")"; |
| } |
| } |
| close(OBJECT_FILE); |
| return %retval; |
| } |
| |
| # string (0xFFFFFFFF), hash(number:string) |
| # return string |
| sub get_name { |
| my $location = hex($_[0]); |
| my $offsets = $_[1]; |
| my %offsets = %$offsets; |
| if (exists $offsets{$location}) { |
| return $offsets{$location}; |
| } else { |
| my $retval = $_[0]; |
| $retval =~ y/[A-Z]/a-z/; |
| warning("No symbol found for $retval"); |
| return $retval; |
| } |
| } |
| |
| # string (filename), hash(number:string) |
| # return array(array(number,number,string)) |
| sub create_list { |
| open(PROFILE_FILE,$_[0]) || |
| error("Could not open profile file: $profile_file"); |
| my $offsets = $_[1]; |
| my $started = 0; |
| my %pfds; |
| # my $totalCalls = 0; |
| # my $totalTicks = 0; |
| # my $pfds = 0; |
| while (<PROFILE_FILE>) { |
| if ($started == 0) { |
| if (m/^0x/) { |
| $started = 1; |
| } else { |
| next; |
| } |
| } |
| my @parts = split(/[[:space:]]+/); |
| if ($parts[0] =~ m/^0x/) { |
| my $callName = get_name($parts[0],$offsets); |
| my $calls = $parts[1]; |
| my $ticks = $parts[2]; |
| my @pfd = ($calls,$ticks,$callName); |
| if (exists $pfds{$callName}) { |
| my $old_pfd = $pfds{$callName}; |
| $pfd[0]+=@$old_pfd[0]; |
| $pfd[1]+=@$old_pfd[1]; |
| } |
| $pfds{$callName} = \@pfd; |
| # $pfds++; |
| # $totalCalls+=$calls; |
| # $totalTicks+=$ticks; |
| } else { |
| last; |
| } |
| |
| } |
| close(PROFILE_FILE); |
| # print("FUNCTIONS\tTOTAL_CALLS\tTOTAL_TICKS\n"); |
| # printf(" %4d\t %8d\t %8d\n",$pfds,$totalCalls,$totalTicks); |
| return values(%pfds); |
| } |
| |
| # array(array(number,number,string)), number (sort element) |
| sub print_sorted { |
| my $pfds = $_[0]; |
| my @pfds = @$pfds; |
| my $sort_index = $_[1]; |
| my $percent = $_[2]; |
| my %elements; |
| my $totalCalls = 0; |
| my $totalTicks = 0; |
| $pfds = 0; |
| foreach $element(@pfds) { |
| $elements{@$element[$sort_index] . @$element[2]} = $element; |
| $pfds++; |
| $totalCalls += @$element[0]; |
| $totalTicks += @$element[1]; |
| } |
| my @keys = sort(keys(%elements)); |
| print("FUNCTIONS\tTOTAL_CALLS\tTOTAL_TICKS\n"); |
| printf(" %4d\t %8d\t %8d\n",$pfds,$totalCalls,$totalTicks); |
| foreach $key(@keys) { |
| my $element = $elements{$key}; |
| if ($percent) { |
| printf("Calls: %7.2f%% Ticks: %7.2f%% Symbol: %s\n", |
| @$element[0]/$totalCalls*100, |
| @$element[1]/$totalTicks*100, |
| @$element[2]); |
| } else { |
| printf("Calls: %08d Ticks: %08d Symbol: %s\n", |
| @$element); |
| } |
| } |
| } |
| |
| # merges two hashes |
| sub merge_hashes { |
| my $hash1 = $_[0]; |
| my $hash2 = $_[1]; |
| return (%$hash1,%$hash2); |
| } |
| |
| sub usage { |
| if (@_) { |
| print STDERR ("Error: @_\n"); |
| } |
| print STDERR ("USAGE:\n"); |
| print STDERR ("$0 profile.out map obj[...] [map obj[...]...] sort[...]\n"); |
| print STDERR |
| ("\tprofile.out output from the profiler, extension is .out\n"); |
| print STDERR |
| ("\tmap map file, extension is .map\n"); |
| print STDERR |
| ("\tobj library or object file, extension is .a or .o\n"); |
| print STDERR |
| ("\tformat 0-2[_p] 0: by calls, 1: by ticks, 2: by name\n"); |
| print STDERR |
| ("\t _p shows percents instead of counts\n"); |
| print STDERR ("NOTES:\n"); |
| print STDERR |
| ("\tmaps and objects come in sets, one map then many objects\n"); |
| exit(1); |
| } |
| |
| |
| if ($ARGV[0] =~ m/-(h|help|-help)/) { |
| usage(); |
| } |
| if (@ARGV < 2) { |
| usage("Requires at least 2 arguments"); |
| } |
| if ($ARGV[0] !~ m/\.out$/) { |
| usage("Profile file must end in .out"); |
| } |
| my $i = 1; |
| my %symbols; |
| { |
| my %map; |
| for (; $i < @ARGV; $i++) { |
| my $file = $ARGV[$i]; |
| if ($file =~ m/\.map$/) { |
| %map = read_map($file); |
| } elsif ($file =~ m/\.[ao]$/) { |
| if (!%map) { |
| usage("No map file found before first object file"); |
| } |
| my @parts = split(/\//,$file); |
| my %new_symbols = read_library($file,$map{$parts[$#parts]}); |
| %symbols = merge_hashes(\%symbols,\%new_symbols); |
| } else { |
| last; |
| } |
| } |
| } |
| if (!%symbols) { |
| warning("No symbols found"); |
| } |
| my @pfds = create_list($ARGV[0],\%symbols); |
| for (; $i < @ARGV; $i++) { |
| print_sorted(\@pfds,split("_",$ARGV[$i])); |
| } |