#!/usr/bin/perl
# Filename:	sim86
# Author:	David Ljung Madison <DaveSource.com>
# See License:	http://MarginalHacks.com/License/
# Description:	Incomplete x86 binary simulator, thanks to Disassemble::X86
# References:
#   Dis:	Disassemble::X86 thanks to Bob Mathews!
#   Insts:	http://www.itis.mn.it/linux/quarta/x86/index.htm
#   Jumps:	http://www.unixwiz.net/techtips/x86-jumps.html
#   Regs:	http://www.campusprogram.com/reference/en/wikipedia/x/x8/x86_assembly_language.html
#   Regs:	http://cs.wwc.edu/~aabyan/Unix/x86.html
#   ISA:	http://x86.org/intel.doc/386manuals.htm
use strict;
use Disassemble::X86;

# For debug
#use Data::Dump qw(dump);

##################################################
# Setup the variables
##################################################
my $PROGNAME = $0;
$PROGNAME =~ s|.*/||;

##################################################
# Usage
##################################################
sub fatal {
  foreach my $msg (@_) { print STDERR "[$PROGNAME] ERROR:  $msg\n"; }
  exit(-1);
}

sub usage {
  foreach my $msg (@_) { print STDERR "ERROR:  $msg\n"; }

  print STDERR <<USAGE;

Usage:\t$PROGNAME <exe>
  Simulates an x86 executable
  -q              Be quiet
  -v              Be verbose
  -V              Be very verbose
  -do <file>      Start by running command file
  -batch <file>   Run a batch command file then exit

USAGE
  exit -1;
}

sub parse_args {
  my %opt;

  while (my $arg=shift(@ARGV)) {
    if ($arg =~ /^-h$/) { usage(); }
    if ($arg =~ /^-d$/) { $MAIN::DEBUG=1; next; }
    if ($arg =~ /^-q$/) { $opt{quiet}=1; next; }
    if ($arg =~ /^-v$/) { $opt{verbose}=1; next; }
    if ($arg =~ /^-V$/) { $opt{verbose}=2; next; }
    if ($arg =~ /^-do$/) { source(\%opt,shift(@ARGV)); next; }
    if ($arg =~ /^-b(atch)?$/) { $opt{batch}=1; source(\%opt,shift(@ARGV)); next; }
    if ($arg =~ /^-ip$/) { $opt{start}{ip}=0+shift @ARGV; next; }
    if ($arg =~ /^-cs$/) { $opt{start}{cs}=0+shift @ARGV; next; }
    if ($arg =~ /^-/) { usage("Unknown option: $arg"); }
    usage("Too many files specified [$arg and $opt{file}]") if $opt{file};
    $opt{file}=$arg;
  }
  usage("No file defined") unless $opt{file};

  \%opt;
}

sub debug {
  return unless $MAIN::DEBUG;
  foreach my $msg (@_) { print STDERR "[$PROGNAME] $msg\n"; }
}

##################################################
# Memory
##################################################
sub set_mem_byte {
  my ($opt,$addr,$val) = @_;
  $opt->{mem}{$addr} = $val;
}
sub get_mem_byte {
  my ($opt,$addr) = @_;
  defined($opt->{mem}{$addr}) ? $opt->{mem}{$addr} : $opt->{meminit}->get_byte($addr);
}

# Little-endian
sub set_mem {
  my ($opt,$addr,$size,$val) = @_;
  printf "[0x%0.8x] <- 0x%0.8x [$size]\n",$addr,$val
    if $opt->{verbose};
  for (my $byte=0; $byte<$size; $byte++) {
    my $byteval = $val & 0xff;
    $val >>= 8;
    set_mem_byte($opt,$addr+$byte,$byteval);
  }
}

sub get_mem {
  my ($opt,$addr,$size,$quiet) = @_;
  my $val = 0;
  for (my $byte=$size-1; $byte>=0; $byte--) {
    my $byteval = get_mem_byte($opt,$addr+$byte);
    $val <<= 8;
    $val |= $byteval;
  }
  printf "[0x%0.8x] -> 0x%0.8x [$size]\n",$addr,$val
    if $opt->{verbose}>1 && !$quiet;
  $val;
}

sub push_stack {
  my ($opt,$val,$size) = @_;
  $size /= 8;
  my $sp = $size==4 ? 'esp' : 'sp';
  my $sp_val = get_reg($opt,$sp);
  $sp_val -= $size;
  set_reg($opt,$sp,$sp_val);
  my $top = calc_seg(get_reg($opt,'ss'), $sp_val);
  set_mem($opt,$top,$size,$val);
}

sub pop_stack {
  my ($opt,$size) = @_;
  $size /= 8;
  my $sp = $size==4 ? 'esp' : 'sp';
  my $sp_val = get_reg($opt,$sp);
  my $top = calc_seg(get_reg($opt,'ss'), $sp_val);
  $sp_val += $size;
  set_reg($opt,$sp,$sp_val);
  get_mem($opt,$top,$size);
}

##################################################
# Registers
##################################################

# ax is 16 bits.  eax is 32 bits.  al is low 8, ah is high 8
# standards:  cx:counter,  dx:i/o,  si:DS data,  di:ES data, sp:stack, bp:SS(stack) data
my @REGS = qw(a b c d si di sp bp);
# code, data, extra, extra2, extra3, stack
my @SEGS = qw(cs ds es fs gs ss);
my @FLAGS = qw(ip flags);
my @CR = qw(cr0 cr2 cr3 cr4 gdtr ldtr idtr);	# Also 'tr..' ??
sub init {
  my ($opt) = @_;

  # Init regs
  foreach my $reg ( @REGS, @SEGS, @FLAGS, @CR ) {
    $opt->{regs}{$reg}{val} = 0;
  }

  # Where should we put the segments??  (Default is segmented memory model)
  $opt->{regs}{cs}{val} = defined $opt->{start}{cs} ? $opt->{start}{cs} : 0x1000;
  $opt->{regs}{ds}{val} = defined $opt->{start}{ds} ? $opt->{start}{ds} : 0x2000;
  $opt->{regs}{ss}{val} = defined $opt->{start}{ss} ? $opt->{start}{ss} : 0x3000;
  $opt->{regs}{es}{val} = defined $opt->{start}{es} ? $opt->{start}{es} : 0x4000;
  $opt->{regs}{fs}{val} = defined $opt->{start}{fs} ? $opt->{start}{fs} : 0x5000;
  $opt->{regs}{gs}{val} = defined $opt->{start}{gs} ? $opt->{start}{gs} : 0x6000;

  # Arbitrary stack pointer
  $opt->{regs}{sp}{val} = 0x3fff;

  # Where do we start?
  set_ip($opt,$opt->{start}{ip});

  # Flags start at 0x2
  $opt->{regs}{flags}{val} = 0x2;
}

# ax is 16 bits.  eax is 32 bits.  al is low 8, ah is high 8
sub which_reg {
  my ($opt,$reg_str,$given_size) = @_;

  my $reg = $reg_str;
  my $ext = ($reg =~ s/^e(..)/$1/) ? 1 : 0;
  my $hi = ($reg =~ s/^([abcd])h/$1/) ? 1 : 0;
  my $lo = ($reg =~ s/^([abcd])l/$1/) ? 1 : 0;
  $reg =~ s/^([abcd])x/$1/;
  fatal("Register [$reg_str] can't be extended, hi and lo")
    if $ext+$hi+$lo > 1;

  return ($opt->{regs}{$reg}?1:0) unless wantarray;

  # Sanity check size
  my $size = $ext ? 32 : ($hi||$lo) ? 8 : 16;
  fatal("Size [$size] doesn't match [$reg_str]") if $given_size && $size != $given_size;

  fatal("Unknown register [$reg_str]") unless $opt->{regs}{$reg};
  ($reg,$size,$hi);
}

sub get_reg {
  my ($opt,$reg_str,$size) = @_;

  (my $reg,$size,my $hi) = which_reg($opt,$reg_str,$size);
  my $val = $opt->{regs}{$reg}{val};
  $val>>=8 if $hi;
  $val&=size_mask($size) if $size;
  return $val unless $opt->{verbose}>1;
  printf "%12s -> 0x%0.8x [%d]","\%$reg_str",$val,$size/8 unless $opt->{quiet}>1;
  show_flags($opt) if $reg eq 'flags';
  print "\n" unless $opt->{quiet}>1;
  $val;
}

sub set_reg {
  my ($opt,$reg_str,$val,$size) = @_;

  (my $reg,$size, my $hi) = which_reg($opt,$reg_str,$size);
  my $mask = size_mask($size);
  $mask<<=8 if $hi;
  $val<<=8 if $hi;
  $opt->{regs}{$reg}{val} &= ~$mask;
  $opt->{regs}{$reg}{val} |= $val&$mask;
  printf "%12s <- 0x%0.8x [%d]\n","\%$reg_str",$opt->{regs}{$reg}{val},$size/8
    if $opt->{verbose};
}


##################################################
# FLAGS
##################################################
#
# FLAG BITS
# ---------
# 21  20  19  18 17 16 15 14 13 12 11 10 9  8  7  6  5  4  3  2  1  0
# ID  VIP VIF AC VM RF 0  NT IOPL  OF DF IF TF SF ZF 0  AF 0  PF 1  CF
# 
# S	Indicates a Status Flag
# C	Indicates a Control Flag
# X	Indicates a System Flag
#
# X	ID Flag (ID)
# X	Virtual Interrupt Pending (VIP)
# X	Virtual Interrupt Flag (VIF)
# X	Alignment Check (AC)
# X	Virtual-8086 Mode (VM)
# X	Resume Flag (RF)
# X	Nested Task (NT)
# X	I/O Privilege Level (IOPL)
# X	Overflow Flag (OF)
# X	Direction Flag (DF)
# X	Interrupt Enable Flag (IF)
# X	Trap Flag (TF)
# S	Sign Flag (SF)
# S	Zero Flag (ZF)
# S	Auxiliary Carry Flag (AF)
# S	Parity Flag (PF)
# S	Carry Flag (CF)

sub show_flags {
  my ($opt) = @_;
  my ($of,$sf,$zf,$af,$pf,$cf) = get_flags($opt);
  print "  of=$of sf=$sf zf=$zf af=$af pf=$pf cf=$cf";
}

# Need to rewrite this
sub set_flags {
  my ($opt,$res,$size,$add,$v1,$v2,$no_carry) = @_;

  my $mask = size_mask($size);

  # Carry is unsigned
  my $cf = 0;
  if ($no_carry) {
    (undef, undef, undef, undef, undef, $cf) = get_flags($opt);
  } elsif (defined $v1) {
    my $uv1 = $v1 & $mask;
    my $uv2 = ($add ? -$v2 : $v2) & $mask;
    $cf = $uv1<$uv2 ? 1 : 0;
  }

  my $pf = 1;
  my $low = $res & 0xff;
  while ($low) {
    $pf=1-$pf if $low&1;
    $low>>=1;
  }
  my $zf = $res ? 0 : 1;
  my $sf = bit($res,$size-1);

  # Negative overflow if doesn't fit in mask
  # Positive overflow if signs change
  my $of = 0;
  $of = 1 if $res != ($res&$mask);
  my $sv1 = bit($v1,$size-1);
  my $sv2 = bit($v2,$size-1);
  $sv2 = 1-$sv2 unless $add;
  $of = 1 if $sv1==$sv2 && $sv1!=$sf;

  my $af = 0;	# Borrow - only for mult and whatnot?

  my $flags = ($of<<11) | ($sf<<7) | ($zf<<6) | ($af<<4) | ($pf<<2) | ($cf<<0);
  $flags |= 0x000202;	# 'IF' and '1' bits set
  $opt->{regs}{flags}{val} = $flags;
  get_reg($opt,'eflags') if $opt->{verbose}>1;	# For verbosity

  $of;
}

sub TMP_set_flags {
  my ($opt,$of,$sf,$zf,$pf,$cf) = @_;

  my $flags = ($of<<11) | ($sf<<7) | ($zf<<6) | ($pf<<2) | ($cf<<0);
  $flags |= 0x000202;	# 'IF' and '1' bits set
  $opt->{regs}{flags}{val} = $flags;
  get_reg($opt,'eflags') if $opt->{verbose}>1;	# For verbosity
}

sub set_flag {
  my ($opt,$flag,$val) = @_;
  
  my ($of, $sf, $zf, $af, $pf, $cf) = get_flags($opt);

  $of = $val if $flag eq 'of';
  $sf = $val if $flag eq 'sf';
  $zf = $val if $flag eq 'zf';
  $af = $val if $flag eq 'af';
  $pf = $val if $flag eq 'pf';
  $cf = $val if $flag eq 'cf';

  my $flags = ($of<<11) | ($sf<<7) | ($zf<<6) | ($af<<4) | ($pf<<2) | ($cf<<0);
  $flags |= 0x000202;	# IF and '1' bits set
  $opt->{regs}{flags}{val} = $flags;
}

sub get_flags {
  my ($opt) = @_;
  my $flags = $opt->{regs}{flags}{val};
  my $of = bit($flags,11);
  my $sf = bit($flags,7);
  my $zf = bit($flags,6);
  my $af = bit($flags,4);
  my $pf = bit($flags,2);
  my $cf = bit($flags,0);

  ($of, $sf, $zf, $af, $pf, $cf);
}

sub get_flag {
  my ($opt,$flag) = @_;

  my $flags = $opt->{regs}{flags}{val};
  return bit($flags,11) if $flag eq 'of';
  return bit($flags,7) if $flag eq 'sf';
  return bit($flags,6) if $flag eq 'zf';
  return bit($flags,4) if $flag eq 'af';
  return bit($flags,2) if $flag eq 'pf';
  return bit($flags,0) if $flag eq 'cf';
  die("Unknown flag? get_flag($flag)\n");
}

##################################################
# PC/IP
##################################################
sub set_ip {
  my ($opt,$pos,$quiet) = @_;
  my $dis = $opt->{dis};

  if ($opt->{break}{ip}{$pos}) {
    $opt->{break}{HIT} = 1;
    printf "\nBREAKPOINT [0x%0.8x]\n",$pos unless $opt->{quiet};
  }

  printf "%12s <- %0.8x [32]\n","\%eip",$pos unless $quiet;
  $opt->{regs}{ip}{val} = $pos;
  $opt->{pc} = calc_seg($opt->{regs}{cs}{val},$pos);
  $dis->pos($opt->{pc});
}

sub next_inst {
  my ($opt,$len) = @_;
  set_ip($opt,$opt->{regs}{ip}{val}+$len,1);
}

sub jump {
  my ($opt,$type) = @_;

  my ($of, $sf, $zf, $af, $pf, $cf) = get_flags($opt);

  return 1 if $type eq 'mp';	# jmp.  Heh.
  return $of==1 if $type eq 'o';
  return $of==0 if $type eq 'no';
  return $sf==1 if $type eq 's';
  return $sf==0 if $type eq 'ns';
  return $zf==1 if $type eq 'e' || $type eq 'z';
  return $zf==0 if $type eq 'ne' || $type eq 'nz';
  return $cf==1 if $type eq 'b' || $type eq 'nae' || $type eq 'c';
  return $cf==0 if $type eq 'nb' || $type eq 'ae' || $type eq 'nc';
  return ($cf==1 || $zf==1) if $type eq 'be' || $type eq 'na';
  return ($cf==0 && $zf==0) if $type eq 'a' || $type eq 'nbe';
  return ($sf!=$of) if $type eq 'l' || $type eq 'nge';
  return ($sf==$of) if $type eq 'ge' || $type eq 'nl';
  return ($zf==1 || $sf!=$of) if $type eq 'le' || $type eq 'ng';
  return ($zf==0 && $sf==$of) if $type eq 'g' || $type eq 'nle';
  return $pf==1 if $type eq 'p';
  return $pf==0 if $type eq 'np';
  return get_reg($opt,'cx')==0 if $type eq 'cxz';
  return get_reg($opt,'ecx')==0 if $type eq 'ecxz';

  fatal("Unknown jump? [j$type]");
}

##################################################
# OPERANDS
##################################################

sub size_mask {
  my ($size) = @_;
  return 0xffffffff if $size==32;
  return 0x0000ffff if $size==16;
  return 0x000000ff if $size==8;
  2**($size)-1;
}

sub bit {
  my ($val,$bit) = @_;
  ($val>>$bit)&1;
}

sub ddump {
  my ($what) = @_;
  print "\nDUMP:\n";
  dump($what);
  exit;
}

sub calc_seg {
  my ($seg,$base) = @_;
  ($seg<<16) | ($base);
}

sub calc_addr {
  my ($opt,$operand) = @_;

  my $size = $operand->{size};
  my $arg = $operand->{arg};
  fatal("Unknown calc_addr type") if $#$arg;
  $arg = $arg->[0];
  #fatal("calc_addr size changed [$size != $arg->{size}]?") if $size != $arg->{size};
  fatal("expected calc_addr size of 32?") if $arg->{size}!=32;

  get_operand($opt,$arg);
}

sub get_operand {
  my ($opt,$operand) = @_;

  my $op = $operand->{op};
  my $size = $operand->{size};
  my $mask = size_mask($size);

  if ($op eq 'reg') {
    my $reg = $operand->{arg}[0];
    my $val = get_reg($opt,$reg, $size);
    return $val;
  }

  if ($op eq 'lit') {
    my $val = $operand->{arg}[0];
#print "Ret lit: $val & $mask\n";
    return $val & $mask;
  }

  if ($op eq '*') {
    my $val1 = get_operand($opt,$operand->{arg}[0]);
    my $val2 = get_operand($opt,$operand->{arg}[1]);
    $val1 = extend($val1,$operand->{arg}[0]{size});
    $val2 = extend($val2,$operand->{arg}[0]{size});
#print "Ret *: $val1 * $val2 [$mask]\n";
    return ($val1*$val2) & $mask;
  }

  if ($op eq '+') {
    my $val1 = get_operand($opt,$operand->{arg}[0]);
    my $val2 = get_operand($opt,$operand->{arg}[1]);
    $val1 = extend($val1,$operand->{arg}[0]{size});
    $val2 = extend($val2,$operand->{arg}[0]{size});
    use integer;	# Signed math here
#print "Ret +: $val1 + $val2 [$mask]\n";
    return ($val1+$val2) & $mask;
  }

  if ($op eq 'seg') {
    my $seg = get_operand($opt,$operand->{arg}[0]);
    my $base = get_operand($opt,$operand->{arg}[1]);
    return calc_seg($seg,$base);
  }

  if ($op eq 'mem') {
    my $addr = calc_addr($opt,$operand);
    my $val = get_mem($opt,$addr,$size/8);
#print "GET MEM [$size] $addr -> $val\n";
    return $val & $mask;
  }
fatal("Unknown operand $operand->{op}\n");
}

sub set_operand {
  my ($opt,$operand,$val) = @_;

  my $size = $operand->{size};
  my $mask = size_mask($size);

  if ($operand->{op} eq 'reg') {
    my $reg = $operand->{arg}[0];
    return set_reg($opt,$reg,$val);
  }
  if ($operand->{op} eq 'mem') {
    my $addr = calc_addr($opt,$operand);
#print "SET MEM [$size] $addr <- $val\n";
    return set_mem($opt,$addr,$size/8,$val);
  }
fatal("unknown set operand $operand->{op}\n");
}

##################################################
# MATH / ALU
##################################################
sub extend {
  my ($val,$size) = @_;
  return $val if $size==32;
  my $extend = bit($val,$size-1) ? -1 : 0;
  my $mask = size_mask($size);
  ($val&$mask) | ($extend&~$mask);
}

sub twos_comp {
  my ($val,$size) = @_;
  ((~$val)+1) & size_mask($size);
}

# Do some math
# Signed:
# 0x7fffffff	2147483647
# 0x7ffffffe	2147483646
# 0x00000001	1
# 0x00000000	0
# 0xffffffff	-1
# 0xfffffffe	-2
# 0x80000001	-2147483647
# 0x80000000	-2147483648
#
# Unsigned:
# 0xffffffff	4294967295
# 0x00000001	1
# 0x00000000	0

sub signed_math {
  my ($opt, $add, $size, $v1, $s1, $v2, $s2, $cin) = @_;

  use integer;	# Crucial!

# For testing
#print "\n";
#print " movl \$$v1, \%eax\n";
#print " movl \$$v2, \%edx\n";
#print " ".($add?'add':'sub')." \%edx, \%eax\n";
#print "\n";
#printf "Add $add - %0.8x %0.8x - $v1 [$s1] & $v2 [$s2] \n",$v1,$v2;

  # Size-prepare the operands
  my $mask = size_mask($size);
  $v1 = extend($v1,$s1) & $mask;
  $v2 = extend($v2,$s2) & $mask;

  # Figure out result
  my $res = $add ? $v1 + $v2 : $v1 - $v2;
  $res &= $mask;

  # Figure out flags

  # SF
  my $sf = bit($res,$size-1);

  # ZF
  my $zf = $res ? 0 : 1;

  # PF
  my $pf = 1;
  my $low = $res & 0xff;
  while ($low) {
    $pf=1-$pf if $low&1;
    $low>>=1;
  }

  # OF: signed: passing the 0x80000000 barrier either direction
  # OF: unsigned: passing the 0x0/0xffffffff barrier either direction
  my $sign1 = bit($v1,$size-1);
  my $sign2 = bit($v2,$size-1);
  # Convert to add
  $sign2 = 1-$sign2 unless $add;
  my $of = ($sign1==$sign2 && $sign1!=$sf) ? 1 : 0;

  # CF:
  my $cf = 0;
  # sub: cf if crossing 0x0->0xffffffff ($v2 > $v1)
  # add: cf if crossing 0xffffffff->0x0 (total is bigger than 0xffffffff)
  if ($add) {
    my $t = $mask-$v2;
    {
      no integer;	# We need an unsigned compare here
      $cf = 1 if ($t&$mask) < ($v1&$mask);
    }
  } else {
    $cf = 1 if $v2 > $v1;
  }

#  printf "RES: %0.8x - $res:  of=$of sf=$sf zf=$zf pf=$pf cf=$cf\n",$res;

  ($res,$of,$sf,$zf,$pf,$cf);
}

# For testing
#my %opt;
#signed_math(\%opt, 0, 32, 4, 32, -5, 32, 0);
#signed_math(\%opt, 1, 32, 4, 32, 5, 32, 0);
#signed_math(\%opt, 0, 32, 100, 32, 103, 32, 1);
#signed_math(\%opt, 1, 32, 100, 32, -103, 32, 0);
#signed_math(\%opt, 0, 32, 103, 32, 100, 32, 0);
#signed_math(\%opt, 1, 32, -103, 32, 100, 32, 0);
#signed_math(\%opt, 1, 32, 0x80000011, 32, 0x80000010, 32, 1);
#signed_math(\%opt, 1, 32, 0xffff8011, 32, 0x80000010, 32, 1);
#signed_math(\%opt, 1, 32, 0x80000011, 32, 0x80000010, 32, 1);
#signed_math(\%opt, 1, 32, 0x80000010, 32, 0x80000011, 32, 1);
#signed_math(\%opt, 1, 32, 0x00000005, 32, 0xfffffffe, 32, 1);
#signed_math(\%opt, 1, 32, 0xfffffffe, 32, 0x00000005, 32, 1);
#signed_math(\%opt, 0, 32, 0x80000011, 32, 0x80000010, 32, 0);
#signed_math(\%opt, 0, 32, 0x80000010, 32, 0x80000011, 32, 1);

##################################################
# SIMULATE
##################################################

# Simulate an instruction
sub sim {
  my ($opt) = @_;

  $opt->{simmed}++;

  my ($pos,$op,$op_str) = get_inst($opt);
  printf "\n# 0x%08x\t%s\n", $pos, $op_str if $opt->{verbose};

  my $code = $op->{op};
  my @operands = $op->{arg} ? @{$op->{arg}} : ();

## testing
#$code = 'add';	# ax = ax - dx
#$opt->{regs}{flags}{val} = 0x00000000;
#$opt->{regs}{a}{val} = 0x80000000;
#$opt->{regs}{d}{val} = 0x0ffff0f3;
#$opt->{regs}{a}{val} = -3;
#$opt->{regs}{d}{val} = -4;
#
#print "\n";
#print " movl \$$opt->{regs}{a}{val}, %eax\n";
#print " movl \$$opt->{regs}{d}{val}, %edx\n";
#print " $code %edx, %eax\n";
#print "\n";

  if ($code eq 'nop') {
    # Nothing, dig?

  #########################
  # Arithmetic
  #########################
  } elsif ($code eq 'add' || $code eq 'adc') {
    my $v1 = get_operand($opt,$operands[0]);
    my $s1 = $operands[0]->{size};
    my $v2 = get_operand($opt,$operands[1]);
    my $s2 = $operands[1]->{size};
    my ($res,$of,$sf,$zf,$pf,$cf)
      = signed_math($opt, 1, $s1, $v1, $s1, $v2, $s2);

    # Just do the carry-in as a separate add.  It's simpler.
    if ($code eq 'adc') {
      my (undef, undef, undef, undef, undef, $cin) = get_flags($opt);
      if ($cin) {
        my ($res2,$of2,$sf2,$zf2,$pf2,$cf2)
          = signed_math($opt, 1, $s1, $res, $s1, $cin, $s1);
        ($res,$of,$sf,$zf,$pf,$cf) = ($res2,$of|$of2,$sf2,$zf2,$pf2,$cf|$cf2);
      }
    }

    TMP_set_flags($opt,$of,$sf,$zf,$pf,$cf);
    set_operand($opt,$operands[0],$res);

  } elsif ($code eq 'cmp') {
    my $val1 = get_operand($opt,$operands[0]);
    my $val2 = get_operand($opt,$operands[1]);
    my $size = $operands[0]->{size};
    my $res = ($val2>$val1) ? ~($val2-$val1-1) : ($val1-$val2);
    set_flags($opt,$res,$size,0,$val1,$val2);

  } elsif ($code eq 'inc') {
    my $v1 = get_operand($opt,$operands[0]);
    my $s1 = $operands[0]->{size};
    my ($res,$of,$sf,$zf,$pf,$cf)
      = signed_math($opt, 1, $s1, $v1, $s1, 1, $s1);

    # INC doesn't set carry flag
    TMP_set_flags($opt,$of,$sf,$zf,$pf, get_flag($opt,'cf'));
    set_operand($opt,$operands[0],$res);

  } elsif ($code eq 'sub' || $code eq 'sbb' || $code eq 'cmp') {
    my $v1 = get_operand($opt,$operands[0]);
    my $s1 = $operands[0]->{size};
    my $v2 = get_operand($opt,$operands[1]);
    my $s2 = $operands[1]->{size};
    my ($res,$of,$sf,$zf,$pf,$cf)
      = signed_math($opt, 0, $s1, $v1, $s1, $v2, $s2);

    # Just do the borrow-in as a separate sub.  It's simpler.
    if ($code eq 'sbb') {
      my (undef, undef, undef, undef, undef, $bin) = get_flags($opt);
      if ($bin) {
        my ($res2,$of2,$sf2,$zf2,$pf2,$cf2)
          = signed_math($opt, 0, $s1, $res, $s1, $bin, $s1);
        ($res,$of,$sf,$zf,$pf,$cf) = ($res2,$of|$of2,$sf2,$zf2,$pf2,$cf|$cf2);
      }
    }

    TMP_set_flags($opt,$of,$sf,$zf,$pf,$cf);
    set_operand($opt,$operands[0],$res) unless $code eq 'cmp';

  } elsif ($code eq 'dec') {
    my $v1 = get_operand($opt,$operands[0]);
    my $s1 = $operands[0]->{size};
    my ($res,$of,$sf,$zf,$pf,$cf)
      = signed_math($opt, 0, $s1, $v1, $s1, 1, $s1);

    # DEC doesn't set carry flag
    TMP_set_flags($opt,$of,$sf,$zf,$pf, get_flag($opt,'cf'));
    set_operand($opt,$operands[0],$res);

  #########################
  # BitOps / Logic
  #########################
  } elsif ($code eq 'shl' || $code eq 'sal') {
    my $val = get_operand($opt,$operands[0]);
    my $cnt = get_operand($opt,$operands[1]) & 0x1f;	# 386 only uses 5bit cnt
    my $size = $operands[0]->{size};
# Shouldn't this be based on size?  Right now all shifts are 32 bits
    my $cf = bit($val,31);
    set_flag($opt,'cf',$cf);
    my $res = $val<<$cnt;
    my $of = $cf!=bit($val,31);
    set_flag($opt,'of',$of) if $cnt==1;
    set_operand($opt,$operands[0],$res);

  } elsif ($code eq 'shr' || $code eq 'sar') {
    my $val = get_operand($opt,$operands[0]);
    my $cnt = get_operand($opt,$operands[1]) & 0x1f;	# 386 only uses 5bit cnt
    my $size = $operands[0]->{size};
    my $of = bit($val,$size);	# Only set if $cnt==1
    my $res = $val>>$cnt;
    $res = extend($res,$size-$cnt) if $code eq 'sar';
    set_flag($opt,'of',$of) if $cnt==1;
    set_operand($opt,$operands[0],$res);

  } elsif ($code eq 'and' || $code eq 'test') {
    my $val1 = get_operand($opt,$operands[0]);
    my $val2 = get_operand($opt,$operands[1]);
    my $size = $operands[0]->{size};
    my $res = ($val1 & $val2) & size_mask($size);
    set_flags($opt,$res,$size);
    set_operand($opt,$operands[0],$res) unless $code eq 'test';

  } elsif ($code eq 'xor') {
    my $val1 = get_operand($opt,$operands[0]);
    my $val2 = get_operand($opt,$operands[1]);
    my $size = $operands[0]->{size};
    my $res = ($val1 ^ $val2) & size_mask($size);
    set_flags($opt,$res,$size);
    set_operand($opt,$operands[0],$res);

  } elsif ($code eq 'or') {
    my $val1 = get_operand($opt,$operands[0]);
    my $val2 = get_operand($opt,$operands[1]);
    my $size = $operands[0]->{size};
    my $res = ($val1 | $val2) & size_mask($size);
    set_flags($opt,$res,$size);
    set_operand($opt,$operands[0],$res);

  } elsif ($code eq 'not') {
    my $val = get_operand($opt,$operands[0]);
    set_operand($opt,$operands[0],~$val);

  #########################
  # Stack/Memory
  #########################
  } elsif ($code eq 'push') {
    # 386 behavior was get_val before set_reg(sp) (for 'push sp')
    my $val = get_operand($opt,$operands[0]);
# Arguably this should be based on the 'D' flag in the SS segment
    my $size = 32;
    push_stack($opt,$val,$size);

  } elsif ($code eq 'pop') {
    my $val = pop_stack($opt,32);
    set_operand($opt,$operands[0],$val);

  } elsif ($code eq 'pusha' || $code eq 'pushad') {
    my $size = $code eq 'pushad' ? 32 : 16;
    my $e = $size==32 ? 'e' : '';
    my $sp = get_reg($opt,$e.'sp');
    push_stack($opt,get_reg($opt,$e.'ax'),$size);
    push_stack($opt,get_reg($opt,$e.'cx'),$size);
    push_stack($opt,get_reg($opt,$e.'dx'),$size);
    push_stack($opt,get_reg($opt,$e.'bx'),$size);
    push_stack($opt,$sp);
    push_stack($opt,get_reg($opt,$e.'bp'),$size);
    push_stack($opt,get_reg($opt,$e.'si'),$size);
    push_stack($opt,get_reg($opt,$e.'di'),$size);

  } elsif ($code eq 'pushf' || $code eq 'pushfd') {
    my $size = $code eq 'pushfd' ? 32 : 16;
    my $e = $size==32 ? 'e' : '';
    push_stack($opt, get_reg($opt,$e.'flags'), 32);	# Doubleword

  } elsif ($code eq 'popf' || $code eq 'popfd') {
    my $size = $code eq 'popfd' ? 32 : 16;
    my $e = $size==32 ? 'e' : '';
    set_reg($opt,$e.'flags', pop_stack($opt,32));	# Doubleword

  } elsif ($code eq 'popa' || $code eq 'popad') {
    my $size = $code eq 'popad' ? 32 : 16;
    my $e = $size==32 ? 'e' : '';
    set_reg($opt,$e.'di', pop_stack($opt,$size));
    set_reg($opt,$e.'si', pop_stack($opt,$size));
    set_reg($opt,$e.'bp', pop_stack($opt,$size));
    pop_stack($opt,$size);
    set_reg($opt,$e.'bx', pop_stack($opt,$size));
    set_reg($opt,$e.'dx', pop_stack($opt,$size));
    set_reg($opt,$e.'cx', pop_stack($opt,$size));
    set_reg($opt,$e.'ax', pop_stack($opt,$size));

  } elsif ($code eq 'lea') {
    my $addr = calc_addr($opt,$operands[1]);
    my $size = $operands[0]->{size};
    $addr &= size_mask($size);
    set_operand($opt,$operands[0],$addr);

  } elsif ($code =~ /^mov(.*)/) {
    my $type = $1;
    # mov is move
    # movzx is move zero extend
    fatal("Unknown mov: [mov$type]")
      unless $type =~ /^|sx|zx$/;
    my $val = get_operand($opt,$operands[1]);
    my $size = $operands[1]->{size};
# mov needs to mask old data in?
    $val = extend($val,$size) if $type eq 'sx';
    if ($type eq '' && $size!=32) {
      my $old = get_operand($opt,$operands[0]);
      my $mask = size_mask($size);
      $val = ($old&~$mask) | ($val&$mask);
    }
    set_operand($opt,$operands[0], $val);

  #########################
  # Internals
  #########################
  } elsif ($code eq 'cmc') {
    set_flag($opt, 'cf', 1-get_flag($opt,'cf'));
  } elsif ($code eq 'clc') {
    set_flag($opt, 'cf', 0);

  #########################
  # Control flow
  #########################
  } elsif ($code =~ /^j(.*)$/) {
    if (jump($opt,$1)) {
      my $dest = get_operand($opt,$operands[0]);
      set_ip($opt,$dest);
      return;	# Skip the next_inst()
    }

  } elsif ($code eq 'enter') {
    my $bytes = get_operand($opt,$operands[0]);
    my $level = get_operand($opt,$operands[1]);
    $level %= 32;

    my $ebp = get_reg($opt,'ebp');
    push_stack($opt,$ebp,32);
    my $fp = get_reg($opt,'esp');
    if ($level>0) {
      for (my $i=1; $i<$level; $i++) {
        $ebp -= 4;
        push_stack($opt,$ebp,32);
      }
      push_stack($opt,$fp,32);
    }
    set_reg($opt,'ebp',$fp);
    set_reg($opt,'esp', get_reg($opt,'esp') - $bytes);

  } elsif ($code eq 'leave') {
    set_reg($opt,'esp',get_reg($opt,'ebp'));
    set_reg($opt,'ebp',pop_stack($opt,32));

  } elsif ($code eq 'ret' || $code eq 'call') {
print STDERR "[$PROGNAME] WARNING: '$code' not implemented.\n";
    $opt->{ret}=1 if $code eq 'ret';

  #########################
  # Unknown?
  #########################
  } else {
    print "[$PROGNAME] UNKNOWN OPCODE! [$code]\n";
exit;
  }

  # Advance to the next instruction (skipped on jump)
  next_inst($opt,$op->{len});
}

##################################################
# Command loop
##################################################
sub get_inst {
  my ($opt,$addr) = @_;

  my $dis = $opt->{dis};
  $addr = defined $addr ? $addr : calc_seg($opt->{regs}{cs}{val},$opt->{regs}{ip}{val});
  $dis->pos($addr);
  my $op_str = $dis->disasm();
  my $op = $dis->op();
  my $pos = $dis->op_start();

  # Sanity
  printf STDERR "Jump to nowhere: 0x%0.8x [try ip=0x40, cs=0?]\n",$pos unless defined $op_str;
  ($pos,$op,$op_str);
}

sub prompt {
  my ($opt,$num) = @_;

  my ($pos,$op,$op_str) = get_inst($opt);

  # Prompt
  printf STDERR "\n$PROGNAME: [0x%0.8x] $op_str : $num/$opt->{simmed}> ",$pos;
}

sub source {
  my ($opt,$file) = @_;
  fatal("Can't currently handle nested source commands, sorry") if $opt->{source};
  return print STDERR "Warning: Can't read sourcefile [$file]\n"
    unless open(SOURCE,"<$file");
  $opt->{source} = $file;
}

sub getcmd {
  my ($opt, $num) = @_;

  if ($opt->{nextcmd}) {
    $_ = delete $opt->{nextcmd};
    print "$_\n";
    return 1;
  }
  if ($opt->{source}) {
    $_ = <SOURCE>;
		s/#.*//;	# Ignore comments in source
    return getcmd($opt, $num) unless !defined $_ || /\S/;
return 1 if defined $_;
    return print if defined $_;
    close SOURCE;
    delete $opt->{source};
  }
  return 0 if $opt->{batch};
  prompt($opt,$num) unless $opt->{quiet};
  $_ = <>;
  return 1 if defined $_;
  return 0;
}

sub cmdloop {
  my ($opt) = @_;

  my @history;
  my $opt_num = '(?:|\s+(\d+))';
  while (getcmd($opt,scalar @history)) {
    chomp;
#    next unless /\S/;
    s/^\s*//;  s/\s*$//;
    my $cmd = $_ || $history[-1];
    next unless $cmd;
    push(@history,$cmd);
    last if $cmd =~ /^(q|quit|exit)$/;
    if ($cmd =~ /^(h(elp)?|\?)$/i) {
      print <<HELP;

$PROGNAME commands:

  help              This menu
  exit,quit         Exit the simulator
  source <file>     Source (or '.') a file

Running:
  s(tep)            single-step assembly instructions
  S(tep)            step instructions until a return
  c(ontinue)        continue until breakpoint
	b(reak) <mem>     set a breakpoint

History:
  history           Show last part of history
  !<num>            Repeat history command
  !-<num>           Repeat -num commands back
  !<str>            Repeat last matching command

Values:
  p(rint) <reg>     Show register value
  p(rint) regs      Show register dump
  p(rint) <mem>     Show memory dump
  d(is) [mem]       Disassembly dump
  <reg> = <val>     Set a register value
  <mem> = <val>     Set memory value
  <mem> = <val> [<size>] Set memory value (by size)

Most commands can be followed by a number to repeat.

HELP
      next;
    }
    if ($cmd =~ /^p(rint)?\s+(\S.*)$/i) {
      my $what = $2;
      my $save = $opt->{verbose};
      $opt->{verbose} = 2;
      if (lc($what) eq 'regs') {
# For TESTING:
#        map get_reg($opt,$_), qw(eax ebx ecx edx esi edi ebp eflags);
        map get_reg($opt,$_), qw(eax ebx ecx edx esi edi esp ebp eip eflags);
        if ($what ne 'regs') {
          map get_reg($opt,$_), qw(cs ds es fs gs ss);
          map get_reg($opt,$_), qw(ecr0 ecr2 ecr3 ecr4 gdtr ldtr idtr);
        }
      } elsif (which_reg($opt,$what)) {
        get_reg($opt,$what);
      } else {
        $what = oct($what) if $what =~ /^0/;
        if ($what =~ /\D/) {
          print STDERR "Error: Unknown print: $what\n";
          next;
        }
        foreach ( 0..3 ) {
          printf "0x%0.8x: ",$what;
          foreach ( 0..3 ) {
            printf " %0.8x", get_mem($opt,$what,4,1);
            $what+=4;
          }
          print "\n";
        }
      }
      $opt->{verbose} = $save;
      next;
    }
    if ($cmd =~ /^d(is)?(\s+(\S.*))?$/i) {
      my $addr = $2 ? $3 : calc_seg($opt->{regs}{cs}{val},$opt->{regs}{ip}{val});
      $addr = oct($addr) if $addr =~ /^0/;
      if ($addr =~ /\D/) {
        print STDERR "Error: Unknown print: $addr\n";
        next;
      }
      foreach ( 0..10 ) {
        my ($pos,$op,$op_str) = get_inst($opt,$addr);
        printf "0x%0.8x: $op_str\n",$addr;
        $addr += $op->{len};
      }
      next;
    }
    if ($cmd =~ /^(\S+)\s*=\s*(\S+)(\s+\[(\d+)\])?$/i) {
      my ($what,$val,$size) = ($1,$2,$4);
      $val = oct($val) if $val =~ /^0/;
      if (which_reg($opt,$what)) {
        print STDERR "Size ignored for register settings\n" if $size;
        set_reg($opt,$what,$val);
        next;
      }
      $what = oct($what) if $what =~ /^0/;
      unless ($what =~ /\D/) {
        set_mem($opt,$what,$size||4,$val);
        next;
      }
      print STDERR "Error: Unknown set: $what = $val\n";
      next;
    }
    if ($cmd =~ /^(source|\.)\s+(.+)$/i) {
      my $save = $opt->{quiet};
      $opt->{quiet} = 1;
      source($opt,$2);
      $opt->{quiet} = $save;
      next;
    }
    if ($cmd =~ /^b(reak)?\s+(.+)$/i) {
      my $what = $2;
      $what = oct($what) if $what =~ /^0/;
      $opt->{break}{ip}{$what} = 1;
      printf "Set breakpoint at 0x%0.8x\n",$what;
      next;
    }
    if ($cmd =~ /^bc\s+(.+)$/i) {
      my $what = $2;
      $what = oct($what) if $what =~ /^0/;
      print $opt->{break}{ip}{$what} ? "Cleared" : "No";
      printf " breakpoint at 0x%0.8x\n",$what;
      $opt->{break}{mem}{$what} = 0;
      next;
    }
    if ($cmd =~ /^s(tep)?$opt_num$/) {
      my $num = $2 || 1;
      foreach (1..$num) { sim($opt); }
      next;         
    }
    if ($cmd =~ /^S(tep)?$opt_num$/) {
      my $num = $2 || 1;
      foreach (1..$num) {
        $opt->{ret} = 0;
        while (!$opt->{ret}) {
          sim($opt);
        }
      }
      next;         
    }
    if ($cmd =~ /^c(ontinue)?$opt_num$/i) {
      my $num = $2 || 1;
      foreach (1..$num) {
        $opt->{break}{HIT} = 0;
        while (!$opt->{break}{HIT}) {
          sim($opt);
        }
      }
      next;         
    }
    # History: Handle !num, !-num and !str
    if ($cmd =~ /^history$/) {
      for(my $i=0; $i<=$#history; $i++) {
        print "  $i  $history[$i]\n";
      }
      next;
    }
    if ($cmd =~ /^!(-)?(\d+)$/) {
      my ($back,$num) = ($1,$2);
      $num = $#history-$num if $back;
      
      if ($num>=0 && $num<=$#history) {
        pop @history;
        $opt->{nextcmd} = $history[$num];
      } else { 
        print STDERR "$num: Event not found\n";
      }
      next; 
    }
    if ($cmd =~ /^!(.+)$/) {
      my $str = $1;
      
      for(my $i=$#history-1; $i>=0; $i--) {
        if ($history[$i] =~ /^$str/) {
          $opt->{nextcmd} = $history[$i];
          last;     
        }
      }
      print STDERR "$str: Event not found\n" unless $opt->{nextcmd};
      next;         
    }
  
    print STDERR "Error: Unknown command: $cmd\n";
  }
}

##################################################
# Main code
##################################################
sub main {
  my $opt = parse_args();

  open(FILE,"<$opt->{file}") || usage("Can't read: $opt->{file}");
  my $data = join('',<FILE>);
  close FILE;

  $opt->{meminit} = Disassemble::X86::MemRegion->new( mem => $data );
  $opt->{dis} = Disassemble::X86->new(format => "Text", text => $data);

  init($opt);
  cmdloop($opt);
}
main();
