#! /usr/bin/perl

use strict;

package Eip2r::SystemMap;

sub new {
  my ($type, $file) = @_;
  my $self = {};
  unless ($file) {
    open(OSRELEASE, "/proc/sys/kernel/osrelease") || die "cannot open /proc/sys/kernel/osrelease";
    $file = <OSRELEASE>;
    close(OSRELEASE);
    $file = "/boot/System.map-$file";
    $file = '' unless -r $file;
  }
  unless ($file) {
    $file = "/boot/System.map" if -r "/boot/System.map";
  }
  return unless $file;
  $self->{file} = $file;
  open(SYSTEMMAP, $file) || die "cannot open $file.";
  while (<SYSTEMMAP>) {
    if (/^(\w{8}) [T|t]/) {
      $self->{min} = $1;
      last;
    }
  }
  close(SYSTEMMAP);
  bless $self, $type;
}

sub search {
  my ($self, $eip) = @_;
  my $file = $self->{file};
  my $procname;

  return 0 if $eip lt $self->{min};

  open(SYSTEMMAP, $file) || die "cannot open $file.";
  while (<SYSTEMMAP>) {
    if (/^(\w{8}) [t|T] (\S+)/) {
      last if $1 gt $eip;
      $procname = $2;
    }
  }
  close(SYSTEMMAP);
  return {eip => $eip, func => $procname};
}

package Eip2r::DumpFile;

sub new {
  my ($type, $file) = @_;
  my $dmpfile = $file;
  my $self = {};
  my ($state, $funcname, $filename, $last);
  $dmpfile =~ s,/,~,g;
  $dmpfile .= '.erd';

  $self->{dmpfile} = $dmpfile;
  bless $self, $type;
  unless (-r $file) {
    return (-f $dmpfile) ? $self : 0;
  }
  if (-f $dmpfile) {
    if ((stat($file))[9] < (stat($dmpfile))[9]) {
      return $self;
    }
  }

  open(IN, "objdump -dl $file |") || die "cannot run objdump -dl $file.";
  open(OUT, ">$dmpfile") || die "cannot open file $dmpfile.";
  $state = 0;
  while (<IN>) {
    if ($state == 0) {
      if (/(\w{8}) <(\S+)>:/) {
	$funcname = $2;
	print OUT "$1\t$funcname\n";
      } elsif (/\S+:\d+/) {
	$filename = $&;
	$state = 1;
      } elsif (/(\w+):/) {
	$last = $1;
      }
    } elsif ($state == 1) {
      if (/(\w+):/) {
	printf OUT "%08x\t%s\t%s\n", hex $1, $funcname, $filename;
      }
      $state = 0;
    }
  }
  printf OUT "%08x\t\t\n", (hex $last) + 1;
  close(IN);
  close(OUT);
  return $self;
}

sub search {
  my ($self, $eip) = @_;
  my $dmpfile = $self->{dmpfile};
  my ($funcname, $filename, $c1, $c2, $c3);

  open(FILE, $dmpfile) || die "cannot open $dmpfile.";
  while (<FILE>) {
    chomp;
    ($c1, $c2, $c3) = split /\t/;
    last if $c1 gt $eip;
    $funcname = $c2;
    $filename = $c3;
  }
  close(FILE);
  return {eip => $eip, func => $funcname, file => $filename};
}

package Eip2r::ProcessMap;

sub new {
  my ($type, $file) = @_;
  my $self = {};
  my @maps;

  return unless $file;
  open(PROCESSMAP, $file) || die "cannot open file $file.";
  while (<PROCESSMAP>) {
    if (/^(\w{8})-(\w{8}) r-xp \w{8} ..:.. \d+\s+(\S+)/) {
      push @maps, {start => $1, end => $2, file => $3, dumpfile => Eip2r::DumpFile->new($3)};
    }
  }
  close(PROCESSMAP);
  $self->{maps} = \@maps;
  bless $self, $type;
}

sub search {
  my ($self, $eip) = @_;
  my @maps = @{$self->{maps}};
  my $entry;

  foreach $entry (@maps) {
    if (($entry->{start} le $eip) && ($eip lt $entry->{end})) {
      if ($eip ge '40000000') { # FIXME?
	$eip = sprintf "%08x", (hex $eip) - (hex $entry->{start});
      }
      return $entry->{dumpfile} ? $entry->{dumpfile}->search($eip) : 0;
    }
  }
  return;
}

package Eip2r;

sub new {
  my ($type, $proc_map, $system_map) = @_;
  my $self = {};
  $self->{cache} = {};
  $self->{system_map} = Eip2r::SystemMap->new($system_map);
  $self->{proc_map} = Eip2r::ProcessMap->new($proc_map);
  bless $self, $type;
}

sub search {
  my ($self, $eip) = @_;
  my $result = $self->{cache}->{$eip};

  return $result if $result; # return if cached.

  $eip = sprintf("%08x", hex $eip); # normalize format
  $result = $self->{proc_map}->search($eip) if $self->{proc_map};
  $result = $self->{system_map}->search($eip) if $self->{system_map} && not $result;
  $result = {eip => $eip} unless $result;

  $self->{cache}->{$eip} = $result; # store to cache.
  return $result;
}

sub usage {
  my $exit_status = shift;
  print <<EOS;

Usage: $0 [ -p POSITION ] [ -m PROCESS_MAP ] [ -s SYSTEM_MAP ] [ -o OUTPUT_FILE ] EIP_FILE
   POSITION    : position of eip in EIP_FILE.
   PROCESS_MAP : output of /proc/<PID>/maps
   SYSTEM_MAP  : System.map of kernel (default: /boot/System.map)
   EIP_FILE    : list of EIP

EOS
  exit($exit_status);
}

sub main {
  use Getopt::Std;
  my $opts = {};
  my ($position, $process_map, $system_map, $out_file);
  my ($eip2r, $line, $eip, $result);

  getopts('hp:m:s:o:', $opts) || usage(1);
  usage(0) if $opts->{h};
  $position = $opts->{p};
  $process_map = $opts->{m};
  $system_map = $opts->{s};
  $out_file = $opts->{o};

  $eip2r = Eip2r->new($process_map, $system_map);
  if ($out_file) {
    open(RESULTS, ">$out_file") || die "cannot open $out_file.\n";
  } else {
    open(RESULTS, ">&1") || die "cannot dup STDOUT.\n";
  }

  while (<>) {
    next if /^#/;
    chomp;
    $line = $_;
    $eip = substr $line, $position, 8;
    $result = $eip2r->search($eip);
    if ($result->{file}) {
      printf RESULTS "%s %s %s\n", $line, $result->{func}, $result->{file};
    } elsif ($result->{func}) {
      printf RESULTS "%s %s\n", $line, $result->{func};
    } else {
      printf RESULTS "%s\n", $line;
    }
  }
  close(RESULTS);
}

Eip2r::main if __FILE__ eq $0;

1;
