package Segment;

require Exporter;

@ISA = qw(Exporter);
@EXPORT_OK = qw(
  new
  page_size
  byte_order
  write_xml_elf_defaults
);

use strict;
use Misc();

my %PLATFORM_TEST = (
  'AddrSize' => 0, 'VirtAddr' => 1, 'Align' => 2, '_SC_PAGESIZE' => 3,
  'LMSB' => 4
);
my @PLATFORM_PRINT = (
  [ 'Address size', 0 ],
  [ 'Byte order', 4 ],
  [ 'Base address', 1 ],
  [ '_SC_PAGESIZE', 3 ],
  [ 'Alignment', 2 ],
  [ 'Pages below base', 5 ],
);
my %PLATFORM = (
  'alpha' => [ 64, '120000000', '10000', '2000', 'L', 0x90000 ],
  'i386'  => [ 32,  '08048000',  '1000', '1000', 'L',  0x8048 ],
  'sparc' => [ 32,  '00010000', '10000', '1000', 'M',    0x10 ]
);

sub page_size($) { return $PLATFORM{shift()}[3]; }
sub byte_order($) { return $PLATFORM{shift()}[4]; }

sub new
{
  my $proto = shift;
  my $class = ref($proto) || $proto;
  my $self  = {};

  $self->{Offset} = undef;
  $self->{VirtAddr} = undef;
  $self->{PhysAddr} = undef;
  $self->{FileSiz} = undef;
  $self->{MemSiz} = undef;
  $self->{Flags} = undef;
  $self->{Align} = undef;
  $self->{AddrSize} = undef;

  bless ($self, $class);
  return $self;
}

# parse output of readelf -l
# $line1 = line with LOAD statement
# $line2 = following line (required for 64 bit)
sub from_readelf($$$)
{
  my $self = shift;
  my $line1 = shift;
  my $line2 = shift;

  my @number = Misc::split_hex($line1);

  # for 32-bit the output looks like this:
  # LOAD 0x000000 0x08048000 0x08048000 0x7e414 0x7e414 R E 0x1000

  # for 64-bit the output looks like this:
  # LOAD           0x0000000000000000 0x0000000120000000 0x0000000120000000
  #                 0x00000000000a5140 0x00000000000a5140  R E    10000

  # in both cases the flags may contain spaces, e.g. "R E"
  # this will confuse the hell out of split(),
  # so we use substr instead

  $self->{TypeName} = $number[1];
  $self->{Offset} = $number[2];
  $self->{VirtAddr} = $number[3];
  $self->{PhysAddr} = $number[4];

  if ($#number == 4)
  { # this is 64-bit
    $self->{AddrSize} = 64;

    my @number = Misc::split_hex($line2);
    $self->{FileSiz} = $number[1];
    $self->{MemSiz} = $number[2];
    $self->{Flg} = substr($line2, 59, 3);
    $self->{Align} = substr($line2, 63);
  }
  elsif ($#number >= 8)
  { # this is 32-bit
    $self->{AddrSize} = 32;

    $self->{FileSiz} = $number[5];
    $self->{MemSiz} = $number[6];
    $self->{Flg} = substr($line1, 64, 3);
    $self->{Align} = substr($line1, 68);
  }
  else
  {
    return "Invalid number of fields.";
  }
  # unfortunately we can encounter value "0" and "0x0" 
  $self->{Align} =~ s/^0x//;
  return undef;
}

# parse output of objdump -l
# $line1 = line with LOAD statement
# $line2 = following line
sub from_objdump($$$)
{
  my $self = shift;
  my $line1 = shift;
  my $line2 = shift;

  my @number = Misc::split_hex($line1);

  # for 32-bit the output looks like this:
  # LOAD off    0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
  #      filesz 0x0007e414 memsz 0x0007e414 flags r-x

  return "Invalid number of fields ($#number)." if ($#number != 9);

  $self->{TypeName} = $number[1];
  $self->{Offset} = $number[3];
  $self->{VirtAddr} = $number[5];
  $self->{PhysAddr} = $number[7];
  my $align = $number[9];

  return "Can't parse alignment" if (!($align =~ s/^2\*\*//));
  $self->{Align} = sprintf('%x', 1 << $align);

  @number = Misc::split_hex($line2);
  $self->{FileSiz} = $number[2];
  $self->{MemSiz} = $number[4];
  $self->{Flg} = $number[6];

  return undef;
}

sub num_eq_by_strcmp($$)
{
  my $a = lc(shift());
  my $b = lc(shift());

  my $prefix = '';
  if ($a =~ s/^0x//)
  {
    return "$b not prefixed with 0x" if (!($b =~ s/^0x//));
    $prefix = '0x';
  }

  $a =~ s/^0+//;
  $b =~ s/^0+//;
  return "$prefix$a != $prefix$b" if ($a ne $b);
  return undef;
}

sub check_platform($$)
{
  my $self = shift;
  my $arch = shift;

  my $platform = $PLATFORM{$arch};
  return "No entry for architecture $arch in \%PLATFORM"
  if (!defined($platform));

  while( my ( $name, $index ) = each %PLATFORM_TEST )
  {
    my $a = $self->{$name};
    next if (!defined($a));
    # return "$name: $a != $b" if (lc($a) ne lc($$platform[$index]));

    my $msg = num_eq_by_strcmp($a, $$platform[$index]);
    return "$name: $msg" if (defined($msg));
  }
  return undef;
}

sub write_xml_elf_defaults($)
{
  my $FILE = shift;
  print $FILE <<BLOCK_1;
<tgroup cols="7">
  <colspec colname="platform" align="left"/>
  <colspec colname="address.size" align="right"/>
  <colspec colname="offset" align="right"/>
  <colspec colname="base.address" align="right"/>
  <colspec colname="alignment" align="right"/>
  <colspec colname="pagesize" align="right"/>
  <colspec colname="byte.order" align="center"/>
  <thead>
    <row>
      <entry>Platform</entry>
BLOCK_1
  for my $column(@PLATFORM_PRINT)
  {
    printf $FILE "      <entry>%s</entry>\n", $$column[0];
  }
  print $FILE <<BLOCK_2;
    </row>
  </thead>
  <tbody>
BLOCK_2

  use constant LITERAL_ENTRY =>
    "    <entry><literal>%s</literal></entry>\n";

  my @key = sort(keys(%PLATFORM));
  for my $key(@key)
  {
    printf $FILE "  <row>\n";
    printf $FILE LITERAL_ENTRY, $key;
    my $platform = $PLATFORM{$key};
    for my $column(@PLATFORM_PRINT)
    {
      printf $FILE LITERAL_ENTRY, $$platform[$$column[1]];
    }
    printf $FILE "  </row>\n";
  }
  printf $FILE "  </tbody>\n</tgroup>\n";
}

sub calc_gap($$)
{
  my $self = shift;
  my $after = shift;

  return (
    hex($after->{Offset}) - hex($self->{Offset}) - hex($self->{FileSiz}),
    hex($after->{VirtAddr}) - hex($self->{VirtAddr}) - hex($self->{MemSiz})
  ) ;
}

1;
