#!/usr/bin/env perl use warnings; use strict; # # # package Symbols; use warnings; use strict; sub new($) { my ($class) = @_; my $self = {}; bless $self, $class; return $self; } sub add { my $self = shift; my $addr = shift; my $name = shift; die("bad symbol add $addr $name") if (!defined($name) or !defined($addr)); # dont overwrite an existing name if (!defined($self->{a2n}{$addr})) { $self->{a2n}{$addr} = $name; } return $self; } sub lookup_addr { my $self = shift; my $addr = shift; return $self->{a2n}{$addr}; } sub all_addrs { my $self = shift; return sort keys(%{$self->{a2n}}); } sub addr2str { my $self = shift; my $addr = shift; my $name = $self->lookup_addr($addr); if (!defined($name)) { $name = sprintf("sym_%08x",$addr); } return $name; } 1; package MemRegions; use warnings; use strict; use IO::File; my $debug = 0; sub new($) { my ($class) = @_; my $self = {}; bless $self, $class; return $self; } sub add { my $self = shift; my ($phys_addr, $size, $filename, $file_offset, $flags) = @_; die("bad MemRegion add") if ( !defined($phys_addr) || !defined($size) || !defined($filename) || !defined($file_offset) ); my $region; $region->{phys_addr} = $phys_addr; $region->{size} = $size; $region->{filename} = $filename; $region->{file_offset} = $file_offset; $region->{flags} = $flags; push @{$self->{region}}, $region; if ($flags & 2) { # anonymous memory has no file backing return; } my $fh = IO::File->new($filename, O_RDONLY); if (!defined($fh)) { warn("Could not open $filename\n"); exit(1); } $region->{fh} = $fh; } sub _addr2region { my $self = shift; my $phys_addr = shift; my $size = shift; my $region; # find the correct region for my $r (@{$self->{region}}) { if ($phys_addr >= $r->{phys_addr} && $phys_addr <= $r->{phys_addr}+$r->{size}) { $region = $r; last; } } return $region; } sub read { my $self = shift; my $phys_addr = shift; my $size = shift; my $region = $self->_addr2region($phys_addr,$size); if (!defined($region)) { printf("unhandled address 0x%08x(0x%x)\n",$phys_addr,$size); return undef; } my $offset = $phys_addr - $region->{phys_addr}; $offset += $region->{file_offset}; if ($debug) { printf("0x%08x(%x) = 0x%08x (%s)\n", $phys_addr,$size, $offset, $region->{filename}, ); } if ($region->{flags} & 2) { # anonymous memory return chr(0)x$size; } $region->{fh}->seek($offset,SEEK_SET); my $buf; $region->{fh}->read($buf,$size); return $buf; } sub all_baseaddr { my $self = shift; my @starts; for my $r (@{$self->{region}}) { push @starts, $r->{phys_addr}; } return @starts; } sub region_size { my $self = shift; my $phys_addr = shift; my $region = $self->_addr2region($phys_addr,4); if (!defined($region)) { return undef; } return $region->{size}; } 1; package main; use IO::File; use Data::Dumper; $Data::Dumper::Indent = 1; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Quotekeys = 0; sub load_configfile { my $db = shift; my $filename = shift; my $fh = IO::File->new($filename, O_RDONLY); if (!defined($fh)) { warn("Could not open $filename\n"); exit(1); } while(<$fh>) { chomp; s/\r//g; # remove whitespace s/^\s+//; # remove comment lines s/^[#].*//; if (m/^include\s+(\S+)/) { load_configfile($db,$1); } elsif (m/^load_memory\s+/) { my @a = split(/\s+/,$_); $db->{regions}->add( eval "$a[1]", eval "$a[2]", $a[3], eval "$a[4]", $a[5] ); } elsif (m/^f\W+/) { my @a = split(/\W+/,$_); # 0 1 2 3 # f table.00021510 1 0x00021510 $db->{symbols}->add(eval "$a[3]", $a[1]); } } } sub validate_pointer { my $db = shift; my $val = shift; # Check it is aligned to 32bits if (!$val & 0x3) { return undef; } if (!defined($db->{regions}->_addr2region($val,4))) { return undef; } return $val; } sub find_pointers { my $db = shift; for my $start ($db->{regions}->all_baseaddr()) { my $end = $start + $db->{regions}->region_size($start); my $i = $start; while ($i < $end) { my $buf = $db->{regions}->read($i,4); my $val = unpack("V",$buf); if (validate_pointer($db,$val)) { $db->{symbols}->add($val,sprintf("ptr_%08x",$val)); $db->{p}{src}{$i} = $val; } $i+=4; } } } sub glom_objects { my $db = shift; my @addrs = $db->{symbols}->all_addrs(); while (@addrs) { my $addr = shift @addrs; my $object; $object->{addr} = $addr; $db->{p}{obj}{$addr} = $object; my $next_addr = $addrs[0]; my $size; if (!defined($next_addr)) { $next_addr = $addr; $size = 0; } else { $size = $next_addr - $addr; } $object->{size} = $size; my $offset = 0; while ($addr < $next_addr) { if (defined($db->{p}{src}{$addr})) { $object->{p}{$offset} = $db->{p}{src}{$addr}; $object->{d}{$offset} = undef; } else { $object->{d}{$offset} = unpack("V",$db->{regions}->read($addr,4)); } $addr += 4; $offset += 4; } } } sub output_dot { my $db = shift; print "digraph structs {\n"; print " rankdir=LR;\n"; print " node [shape=record];\n"; print "\n"; for my $addr (sort keys(%{$db->{p}{obj}})) { my $object = $db->{p}{obj}{$addr}; my $name = $db->{symbols}->addr2str($addr); my @ports; push @ports,"

$name:"; for my $offset (sort {$a <=> $b} keys(%{$object->{d}})) { my $val = $object->{d}{$offset}; if (!defined($val)) { # this is a pointer my $dst = $object->{p}{$offset}; my $dstname = $db->{symbols}->addr2str($dst); printf(" %s:p%i -> %s:p;\n",$name,$offset,$dstname); push @ports, sprintf("%s",$offset,$dstname); } else { push @ports, sprintf("0x%08x",$offset,$val); } } printf(" %s [label=\"%s\"]; // %i\n", $name, join("|",@ports), $object->{size} ); printf("\n"); } print "}\n"; } sub main() { my $configfile = shift @ARGV; if (!defined($configfile)) { die('need configfile'); } my $db = {}; $db->{symbols} = Symbols->new(); $db->{regions} = MemRegions->new(); load_configfile($db,$configfile); $db->{symbols}->add($db->{zone}{start},"_start"); find_pointers($db); glom_objects($db); if (@ARGV) { print Dumper($db); } output_dot($db); } main();