diff options
author | Michael Brown | 2010-12-04 03:55:11 +0100 |
---|---|---|
committer | Michael Brown | 2010-12-09 14:11:27 +0100 |
commit | ea0fcb9460403a16d4e2a5e1d48ee6279794450c (patch) | |
tree | ea6bb728ad797317ad93551eb1678c08cf874e81 /src/util/fnrec.pl | |
parent | [pxe] Set correct PktType in PXENV_UNDI_ISR (diff) | |
download | ipxe-ea0fcb9460403a16d4e2a5e1d48ee6279794450c.tar.gz ipxe-ea0fcb9460403a16d4e2a5e1d48ee6279794450c.tar.xz ipxe-ea0fcb9460403a16d4e2a5e1d48ee6279794450c.zip |
[fnrec] Enhance function recording
Enhance the information collected by the function recorder to include
the call site and entry/exit counts. This allows fnrec.pl to produce
a call tree such as:
step (from core/getkey.c:46 = 0x17e90) {
ref_increment (from core/process.c:93 = 0x73ec) { }
net_step (from core/process.c:96 = 0x73f1) {
net_poll (from net/netdevice.c:741 = 0xbce6) {
netdev_poll (from net/netdevice.c:700 = 0xbc58) { }
netdev_rx_dequeue (from net/netdevice.c:709 = 0xbc65) { }
}
}
ref_decrement (from core/process.c:96 = 0x73f9) { }
}
Note that inlined functions are reported, confusingly, as extra calls
to the *containing* function. Minimise this confusion by adding the
attribute "no_instrument_function" to all functions declared as
inline. (Static functions that have been inlined autonomously by gcc
will still be problematic, but these are far fewer in number.)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/util/fnrec.pl')
-rwxr-xr-x | src/util/fnrec.pl | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/src/util/fnrec.pl b/src/util/fnrec.pl new file mode 100755 index 00000000..9a2b3d81 --- /dev/null +++ b/src/util/fnrec.pl @@ -0,0 +1,145 @@ +#!/usr/bin/perl -w +# +# Copyright (C) 2010 Michael Brown <mbrown@fensystems.co.uk>. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +=head1 NAME + +fnrec.pl + +=head1 SYNOPSIS + +fnrec.pl [options] bin/image.xxx < logfile + +Decode a function trace produced by building with FNREC=1 + +Options: + + -m,--max-depth=N Set maximum displayed function depth + +=cut + +use IPC::Open2; +use Getopt::Long; +use Pod::Usage; +use strict; +use warnings; + +use constant MAX_OPEN_BRACE => 10; +use constant MAX_COMMON_BRACE => 3; +use constant MAX_CLOSE_BRACE => 10; + +# Parse command-line options +my $max_depth = 16; +Getopt::Long::Configure ( 'bundling', 'auto_abbrev' ); +GetOptions ( + 'help|h' => sub { pod2usage ( 1 ); }, + 'max-depth|m=i' => sub { shift; $max_depth = shift; }, +) or die "Could not parse command-line options\n"; +pod2usage ( 1 ) unless @ARGV == 1; +my $image = shift; +my $elf = $image.".tmp"; +die "ELF file ".$elf." not found\n" unless -e $elf; + +# Start up addr2line +my $addr2line_pid = open2 ( my $addr2line_out, my $addr2line_in, + "addr2line", "-f", "-e", $elf ) + or die "Could not start addr2line: $!\n"; + +# Translate address using addr2line +sub addr2line { + my $address = shift; + + print $addr2line_in $address."\n"; + chomp ( my $name = <$addr2line_out> ); + chomp ( my $file_line = <$addr2line_out> ); + ( my $file, my $line ) = ( $file_line =~ /^(.*):(\d+)$/ ); + $file =~ s/^.*\/src\///; + my $location = ( $line ? $file.":".$line." = ".$address : $address ); + return ( $name, $location ); +} + +# Parse logfile +my $depth = 0; +my $depths = []; +while ( my $line = <> ) { + chomp $line; + $line =~ s/\r//g; + ( my $called_fn, my $call_site, my $entry_count, my $exit_count ) = + ( $line =~ /^(0x[0-9a-f]+)\s+(0x[0-9a-f]+)\s+([0-9]+)\s+([0-9]+)$/ ) + or print $line."\n" and next; + + ( my $called_fn_name, undef ) = addr2line ( $called_fn ); + ( undef, my $call_site_location ) = addr2line ( $call_site ); + $entry_count = ( $entry_count + 0 ); + $exit_count = ( $exit_count + 0 ); + + if ( $entry_count >= $exit_count ) { + # + # Function entry + # + my $text = ""; + $text .= $called_fn_name." (from ".$call_site_location.")"; + if ( $exit_count <= MAX_COMMON_BRACE ) { + $text .= " { }" x $exit_count; + } else { + $text .= " { } x ".$exit_count; + } + $entry_count -= $exit_count; + if ( $entry_count <= MAX_OPEN_BRACE ) { + $text .= " {" x $entry_count; + } else { + $text .= " { x ".$entry_count; + } + my $indent = " " x $depth; + print $indent.$text."\n"; + $depth += $entry_count; + $depth = $max_depth if ( $depth > $max_depth ); + push @$depths, ( { called_fn => $called_fn, call_site => $call_site } ) x + ( $depth - @$depths ); + } else { + # + # Function exit + # + my $text = ""; + if ( $entry_count <= MAX_COMMON_BRACE ) { + $text .= " { }" x $entry_count; + } else { + $text .= " { } x ".$entry_count; + } + $exit_count -= $entry_count; + if ( $exit_count <= MAX_CLOSE_BRACE ) { + $text .= " }" x $exit_count; + } else { + $text .= " } x ".$exit_count; + } + $depth -= $exit_count; + $depth = 0 if ( $depth < 0 ); + if ( ( @$depths == 0 ) || + ( $depths->[$depth]->{called_fn} ne $called_fn ) || + ( $depths->[$depth]->{call_site} ne $call_site ) ) { + $text .= " (from ".$called_fn_name." to ".$call_site_location.")"; + } + splice ( @$depths, $depth ); + my $indent = " " x $depth; + print substr ( $indent.$text, 1 )."\n"; + } +} + +# Clean up addr2line +close $addr2line_in; +close $addr2line_out; +waitpid ( $addr2line_pid, 0 ); |