#!/usr/bin/perl -w # gen-calltree-dumpbin.pl: Generate a call tree from an ASM listing # generated from Microsoft's "dumpbin.exe /disasm " command. Algorithm # is rather straightforward: Look for lines that have words at the start of # the line. Those delimit a new function. Collect 'call' ASM statements # inside. Also, accumulate reference counts for each function that is invoked; # a function with no references will be classified aws a top-level function. # by Mike Melanson (mike at multimedia.cx) # USAGE: gen-calltree-dumpbin.pl < inputfile.asm.txt > treefile.txt use strict; my %master_function_table; my $function_key; # hash table key my $function_prototype; my $called_function; my $parsing = 0; my %functions_seen; # This function recursively displays all the called functions for a particular # function. Usage: # print_call_tree($function_table_key, $indentation_space_count); sub print_call_tree { my $function_key = shift; my $indentation = shift; my $i; my @sub_functions; for ($i = 0; $i < $indentation; $i++) { print " "; } if ($indentation) { print "+- "; } print "$function_key\n"; if ($master_function_table{$function_key}->{'calls'} ne "") { @sub_functions = split /\|/, $master_function_table{$function_key}->{'calls'}; for ($i = 0; $i <= $#sub_functions; $i++) { print_call_tree($sub_functions[$i], $indentation + 2); } } } # This function prints a list of functions that a function calls. # print_function_list($function_table_key); sub print_function_list { my $function_key = shift; my $i; my @sub_functions; print "-> $function_key\n"; if ($function_key ne $master_function_table{$function_key}->{'prototype'}) { print " proto: $master_function_table{$function_key}->{'prototype'}\n"; } if ($master_function_table{$function_key}->{'calls'} ne "") { @sub_functions = split /\|/, $master_function_table{$function_key}->{'calls'}; for ($i = 0; $i <= $#sub_functions; $i++) { print " $sub_functions[$i]\n"; } } printf "\n"; } # kick off the proceedings print "gen-calltree-dumpbin.pl...\n"; # general analysis loop while ($_ = ) { chop; # look for this string to identify original binary file if (/Dump of file /) { s/Dump of file //; print "call tree analysis based on disassembly of\n $_\n\n"; } # find a particular string before general analysis... if (!$parsing) { if (/File Type: LIBRARY/) { $parsing = 1; next; } else { next; } } # look for lines starting with non-whitespace characters; also '$' does # not count as that indicates a control label if (/^[^\s\$]/) { # check if function has a prototype in parentheses if (/ \(/) { s/^(\S+) (.+)$//; $function_key = $1; $function_prototype = $2; } else { # otherwise, it is just a C-style declaration s/://; # take off the trailing ':' $function_key = $function_prototype = $_; } # initialize entry in hash table (if not already there) if (!defined $master_function_table{$function_key}) { $master_function_table{$function_key}->{'ref_count'} = 0; $master_function_table{$function_key}->{'calls'} = ""; } $master_function_table{$function_key}->{'prototype'} = $function_prototype; undef(%functions_seen); } else { # check for a function call instruction if (/ call /) { # need special provision for dynamic calls since that confuses Perl's # regex engine if (/\[/) { $called_function = "dynamic"; } else { s/^.*call\s+//; $called_function = $_; } # note this call under the current function, if it is not there already if (! defined $functions_seen{$called_function}) { $master_function_table{$function_key}->{'calls'} .= "$called_function|"; $functions_seen{$called_function} = 1; } # bump the called function's reference count (and initialize if it is not # already in the table) if (!defined $master_function_table{$called_function}) { $master_function_table{$called_function}->{'prototype'} = $called_function; $master_function_table{$called_function}->{'ref_count'} = 0; $master_function_table{$called_function}->{'calls'} = ""; } $master_function_table{$called_function}->{'ref_count'}++; } } } # print the final analysis my @values = values(%master_function_table); print "$#values named functions identified (this may include some register names)\n"; print "****************************************************************************\n"; print " these functions are not called and are assessed to be top-level functions:\n"; print "****************************************************************************\n"; foreach $function_key(sort keys %master_function_table) { if (!$master_function_table{$function_key}->{'ref_count'}) { # print_call_tree($function_key, 0); print_function_list($function_key); } } print "****************************************************************************\n"; print " these functions were called in the code:\n"; print "****************************************************************************\n"; foreach $function_key(sort keys %master_function_table) { if ($master_function_table{$function_key}->{'ref_count'}) { print_function_list($function_key); } }